51nod 最大M子段和v1/v2/v3

学习笔记

最大M子段和 V1

\(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;
} 
posted @ 2024-09-10 09:16  sad_lin  阅读(1)  评论(0编辑  收藏  举报