最長M欄位和(1052 1115 1053)
阿新 • • 發佈:2020-12-02
這三個版本最大的區別就是資料範圍的區別:N<=5000時,用n^2的dp可以過;當n達到50000時,用nlogn的dp可以過。
51nod 1052:
設dp[i][j]表示前j個數分成i段的最大欄位和,轉移方程由:dp[i][j]=max(max(dp[i-1][k](k=1...j-1)),dp[i][j])+a[j]);
由於空間會超限,可採用滾動陣列;用f[j]陣列記錄第i-1的前j的最大欄位和。
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=6e3+100; ll dp[N][View Code2]; ll f1[N],f2[N]; int a[N]; int main() { //freopen("1.txt","r",stdin); int n,m; cin>>n>>m; int res=0; ll ans=0,sum=0; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); if(a[i]>0) { res++; sum+=a[i]; } }if(m>=res) { printf("%lld\n",sum); return 0; } for(int i=1;i<=m;i++) { // memset(f1,0,sizeof f1); for(int j=1;j<=n;j++) { dp[i][j&1]=max(f2[j-1],dp[i][(j-1)&1])+a[j]; f1[j]=max(f1[j-1],dp[i][j&1]);if(i==m) ans=max(ans,dp[i][j&1]); } memcpy(f2,f1,sizeof f1); } cout<<ans<<endl; return 0; }
51nod 1053:
我們考慮先把原陣列的相同正負性的數合併,原陣列變成正負相間的數,要求只包含m個欄位的最大數值和,等價於將陣列中的正數連通塊變為m個的最大收益;
有兩個操作可以讓整數連通塊減少:1.將兩個正數聯通塊與中間的負數連通塊合併,答案減去負數的絕對值。
2.減去一個正數連通塊,貢獻減去正數連通塊的絕對值。
因此,無論哪個操作都是減去聯通塊的絕對值,我們考慮將每個連通塊以絕對值為貢獻排序,儘量用絕對值小的減去正數連通塊的數量,這樣一定是最優的。
為了方便合併,我們採用雙向連結串列進行操作。注意:當負數連通塊在連結串列兩端時,操作無效。
為了方便操作,採用set方便配合連結串列進行刪除。
#define ll long long #define x first #define y second #include<bits/stdc++.h> using namespace std; const int N=1e5+100; ll sum[N], a[N]; int l[N],r[N]; void modify(int u)//刪去連結串列某個點 { int l1=l[u],rr=r[u]; if(l1) r[l1]=rr; if(rr) l[rr]=l1; } int main() { //freopen("1.txt","r",stdin); int n,m; int cnt=0; cin>>n>>m; ll ans=0; int res=0; for(int i=1;i<=n;i++) { int x; scanf("%d",&x); if(x) a[++cnt]=x; if(x>0)ans+=x,res++; } if(m>=res) { cout<<ans<<endl; return 0; } res=0; ll u=0; n=cnt; cnt=0; for(int i=1,j;i<=n;i++) { j=i; u=a[i]; while(j+1<=n&&a[j]*a[j+1]>0) { u+=a[j+1]; j++; } if(!cnt&&u<0)continue; if(u<0&&i==n)continue; if(u>0)res++; sum[++cnt]=u; i=j; } for(int i=2;i<=cnt;i++) { l[i]=i-1; r[i-1]=i; } set<pair<ll,int> >s; for(int i=1;i<=cnt;i++) { s.insert({abs(sum[i]),i}); //cout<<sum[i]<<endl; } //cout<<ans<<endl; for(int i=res;i>m;) { int id=s.begin()->second; s.erase(s.begin()); if(sum[id]<0&&(!l[id]||!r[id]))continue;//當負數連通塊在兩端時無效 //cout<<sum[id]<<endl; ans-=abs(sum[id]);//減去代價 sum[id]+=sum[l[id]]+sum[r[id]]; if(l[id]) { s.erase({abs(sum[l[id]]),l[id]}); modify(l[id]); } if(r[id]) { s.erase({abs(sum[r[id]]),r[id]}); modify(r[id]); } if(sum[id]) s.insert({abs(sum[id]),id}); i--; } cout<<ans<<endl; return 0; }View Code
51nod 1115
這題與上題的唯一區別就是變成了環形操作。
將雙向連結串列變為環形雙向連結串列,初始的首末連通塊如果正負性相同則需合併。其餘與上題相同。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 100010; int n,m,now; ll sum[N]; ll a[N]; int l[N],r[N]; int main() { //freopen("2.txt","r",stdin); cin>>n>>m; int p=0; int res=0; ll ans=0; for(int i=1;i<=n;i++) { ll x; scanf("%lld",&x); if(x!=0) a[++p]=x; if(x>0) { res++; ans+=x; } } if(m>=res) { cout<<ans<<endl; return 0; } n=p; int cnt=0; p=0; res=0; ll u=0; for(int i=1,j;i<=n;i++) { j=i; u=a[i]; while(j+1<=n&&a[j+1]*a[j]>0) { u+=a[j+1]; j++; } i=j; sum[++cnt]=u; } set<pair<ll,int> > s; if(sum[1]*sum[cnt]>0)//首末連通塊正負性相同則合併 { sum[1]+=sum[cnt]; cnt--; } for(int i=1;i<=cnt;i++) { //cout<<sum[i]<<endl; if(sum[i]>0)res++; s.insert({abs(sum[i]),i}); } for(int i=2;i<=cnt;i++) { l[i]=i-1; r[i-1]=i; } p=cnt; r[cnt]=1; l[1]=cnt; for(int i=res;i>m;) { int id=s.begin()->second; s.erase(s.begin()); ans-=abs(sum[id]); sum[id]+=sum[l[id]]+sum[r[id]]; if(l[id]) { s.erase({abs(sum[l[id]]),l[id]}); int cur=l[id]; int ll=l[cur],rr=r[cur]; if(ll) { r[ll]=rr; } if(rr) { l[rr]=ll; } } if(r[id]) { s.erase({abs(sum[r[id]]),r[id]}); int cur=r[id]; int ll=l[cur],rr=r[cur]; if(ll) { r[ll]=rr; } if(rr) { l[rr]=ll; } } if(sum[id]) s.insert({abs(sum[id]),id}); i--; } printf("%lld\n",ans); return 0; }View Code