这三个版本最大的区别就是数据范围的区别: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][2]; 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; }View Code
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