51nod 最大M子段和v1/v2/v3
\(N\) 个整数组成的序列 \(a[1],a[2],a[3],…,a[n]\),将这N个数划分为互不相交的 \(M\) 个子段,并且这 \(M\) 个子段的和是最大的。如果 \(M >= N\) 个数中正数的个数,那么输出所有正数的和。\(N,M<=5000\)。
例如:\(-2 11 -4 13 -5 6 -2\),分为 \(2\) 段,\(11 -4 13\) 一段,\(6\) 一段,和为 \(26\)。
V2
\(N\) 个整数组成的序列 \(a[1],a[2],a[3],…,a[n]\),将这 \(N\) 个数划分为互不相交的 \(M\) 个子段,并且这 \(M\) 个子段的和是最大的。如果 \(M >= N\) 个数中正数的个数,那么输出所有正数的和。 \(N,M<=50000\)。
例如:\(-2 11 -4 13 -5 6 -2\),分为 \(2\) 段,\(11 -4 13\) 一段,\(6\) 一段,和为 \(26\)。
最大M子段和 V3
环形最大 \(M\) 子段和,\(N\) 个整数组成的序列排成一个环,\(a[1],a[2],a[3],…,a[n]\)( \(a[n], a[1]\) 也可以算作1段),将这 \(N\) 个数划分为互不相交的 \(M\) 个子段,并且这 \(M\) 个子段的和是最大的。如果 \(M>=N\) 个数中正数的个数,那么输出所有正数的和。\(N,M<=100000\)。
例如:\(-2 11 -4 13 -5 6 -1\),分为 \(2\) 段,\(6 -1 -2 11\) 一段,\(13\) 一段,和为 \(27\)。
v2 是 v1 的升级版,而v3是v2的特殊版(更好处理了)所以我们先讲v2,我们将连续的正数和连续的负数都连成一段,对每个段用链表储存,对每一段的值用优先队列储存绝对值,对于绝对值小的段考虑将他两边的段合并,更新值。
看代码好理解,文字真的没啥意思。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e4+5;
int n,m;
int a[N];//原值
int k;//总段数
int p;//正数段数
ll s;//正数和
ll ans;//总答案
ll c[N];
int l[N],r[N];//左右的段的编号
int tra[N];//标记是否被合并
struct cmp{
bool operator()(const int x,const int y){
return abs(c[x])>abs(c[y]);//绝对值排序
}
};
priority_queue<int,vector<int>,cmp> q;
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
if((ll)a[i]*a[i-1]<0){//异号
if(s>0){//是正数要储存答案
ans+=s;//先求正数总和
++p;//正数段数增加
}
c[++k]=s;//开新段
s=a[i];//段正常增加
}
else{
s+=a[i];//同号增加增加
}
}
if(s>0){//因为边界
ans+=s;//累加
p++;//正数段数增加
}
if(s){
c[++k]=s;//非零新开一段
}
//链表操作
r[1]=2;
q.push(1);
for(int i=2;i<k;i++){
l[i]=i-1;
r[i]=i+1;
q.push(i);
}
l[k]=k-1;
q.push(k);
int le=p-m;//需要合并几个段
if(le<=0){//已经可以输出了
cout<<ans;
return 0;
}
int now;
while(1){
now=q.top();
q.pop();
if(tra[now]){
continue;
}
if(c[now]<=0&&(!l[now]||!r[now])){//负数且在最左和最右
continue;
}
ans-=abs(c[now]);//合并了减去
c[now]+=c[l[now]]+c[r[now]];//合并值
if(l[now]){//能左合并
tra[l[now]]=1;
l[now]=l[l[now]];
r[l[now]]=now;
}
if(r[now]){//能右合并
tra[r[now]]=1;
r[now]=r[r[now]];
l[r[now]]=now;
}
q.push(now);
le--;//减少合并次数
if(!le){
break;
}
}
cout<<ans;
return 0;
}
看懂上面了,v3 直接就知道怎么操作,无非就是链表上进行操作。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5;
int n,m;
int a[N];
int k;
int p;
ll s;
ll ans;
ll c[N];
int l[N],r[N];
int tra[N];
struct cmp{
bool operator()(const int x,const int y){
return abs(c[x])>abs(c[y]);
}
};
priority_queue<int,vector<int>,cmp> q;
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
if((ll)a[i]*a[i-1]<0){
if(s>0){
ans+=s;
++p;
}
c[++k]=s;
s=a[i];
}
else{
s+=a[i];
}
}
if(s>0){
ans+=s;
if(c[1]<0){//无法首尾合并
++p;
}
}
if(s){
if(c[1]*s<0){//无法首尾合并
c[++k]=s;
}
else{
c[1]+=s;//能合并
}
}
//这注意改
l[1]=k;
r[1]=2;
q.push(1);
for(int i=2;i<k;i++){
l[i]=i-1;
r[i]=i+1;
q.push(i);
}
l[k]=k-1;
r[k]=1;
q.push(k);
int le=p-m;
if(le<=0){
cout<<ans;
return 0;
}
int now;
while(1){
now=q.top();
q.pop();
if(tra[now]){
continue;
}
if(c[now]<=0&&(!l[now]||!r[now])){
continue;
}
ans-=abs(c[now]);
c[now]+=c[l[now]]+c[r[now]];
//直接合并
tra[l[now]]=1;
l[now]=l[l[now]];
r[l[now]]=now;
tra[r[now]]=1;
r[now]=r[r[now]];
l[r[now]]=now;
q.push(now);
le--;
if(!le){
break;
}
}
cout<<ans;
return 0;
}