51nod 最大M子段和系列(1052、1053、1115)
-
51nod1052
数据量小,可使用O(N*M)的DPAC,递推公式:
dp[i][j]=max(dp[i-1][j-1], dp[i][j-1])+a[j];
dp[i][j]表示前j个数取 i 段的最大子段和,用滚动数组思想优化空间。
-
51nod1053、51nod1115
进阶版并不使用dp,容易被第一题的思维误导钻到死胡同里。
- 可以先做一下处理以便思考,将原序列连续的正数和连续的负数合并,即可得到一个正负交替的序列;
- 设新的序列中有k个正数,若m>=k则输出所有正数的和;
- 接着考虑m<k的情况,首先所有正数的和(设为S)是我们的初始状态,但因m<k,故我们需要放弃一些正数,或者合并两个相邻的正数(两正数之间的复数也要合并,则相当于额外要一个负数);
- 显然我们肯定要放弃那些绝对值更小的正数或附加绝对值更小的负数,这显然是一个贪心问题了;
- 既然是以绝对值为标准选数,不妨将所有负数变成正数,然后选出k-m个最小的数来,然后S减去他们就是结果;
- 以上可以通过维护一个堆(优先队列)来达到目的,但要处理几个问题;
- 每选出一个数,需要将它与其相邻的两个数合并后重新加入堆,而原数要重堆中删除是个麻烦的事情;
- 这里可以通过额外的标记处理,所以还需要维护每个数实时的“左邻右舍”,V2需要处理一下边界问题,V3则更简单些;
- 代码如下:
#include<iostream> #include<cstring> #include<queue> #define LL long long using namespace std; const LL maxn = 1e5+5, INF = 1e9; int a[maxn], l[maxn], r[maxn]; bool vis[maxn]; LL s[maxn]; priority_queue< pair<LL, int> > q; int main() { //freopen("stdin","r",stdin); int n, m, t; cin>>n>>m; int cnt=1; LL sum=0; cin>>s[1]; for(int i=2; i<=n; ++i) { cin>>t; if((t>=0) != (s[cnt]>=0)) s[++cnt]=t; else s[cnt]+=t; } //cout<<s[0]<<endl<<s[cnt]<<endl<<endl; while(s[cnt]*s[1]>=0) s[1]+=s[cnt--]; t=0; for(int i=1; i<=cnt; ++i) { //cout<<s[i]<<endl; vis[i]=false; l[i]=i-1; r[i]=i+1; if(s[i]>0) { sum+=s[i]; s[i]=-s[i]; t++; } q.push( make_pair(s[i],i)); } l[1]=cnt; r[cnt]=1; cnt=t; if(m>=cnt) { cout<<sum<<endl; return 0; } while(!q.empty() && cnt>m) { pair<LL, int> tmp=q.top(); q.pop(); if(vis[tmp.second]) { continue; } else { cnt--; sum+=s[tmp.second]; s[tmp.second]=s[l[tmp.second]]+s[r[tmp.second]]-s[tmp.second]; q.push( make_pair(s[tmp.second], tmp.second)); vis[l[tmp.second]]=true; vis[r[tmp.second]]=true; r[l[l[tmp.second]]]=tmp.second; l[r[r[tmp.second]]]=tmp.second; l[tmp.second]=l[l[tmp.second]]; r[tmp.second]=r[r[tmp.second]]; } } cout<<max(0ll,sum)<<endl; return 0; }