Nowcoder9984I.九峰与分割序列(线段树+DP)

I.九峰与分割序列

题意:

给出一个序列,将其分割成若干个子区间。

子区间的贡献为:若前一个子区间的长度大于k且该区间长度小于等于k,则贡献为区间和的两倍,否则贡献为区间和。

求一种分割方法,使得所有子区间贡献之和最大,输出最大贡献。

题解:

考虑DP。

定义(f(i,0))表示最后一段区间大于k的最优解。

定义(f(i,0))表示最后一段区间小于等于k的最优解。

可以写出以下转移方程:

(f(i,0)=max(f(i,0),max_{j=1}^{i-k-1}f(j,0)+sum_i-sum_j))

(f(i,0)=max(f(i,0),max_{j=1}^{i-k-1}f(j,1)+sum_i-sum_j))

(f(i,1)=max(f(i,1),max_{j=i-k}^{i-1}f(j,0)+sum_i*2-sum_j*2))

(f(i,1)=max(f(i,1),max_{j=i-k}^{i-1}f(j,1)+sum_i-sum_j))

这个转移方程看起来很难快速维护。

有一个巧妙的思路,就是把和(j)有关的项合并,这样就可以用线段树维护了。

开三棵线段树。

第一棵维护(f(i,0)-sum_i)

第二棵维护(f(i,1)-sum_i)

第三棵维护(f(i,0)-sum_i*2)

这样在转移的过程中,从对应的线段树里查询,最后再把每个状态更新到对应的线段树里就行了。

// Problem: 九峰与分割序列
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/9984/I
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+100;
int n,k,a[maxn];

struct node {
	int i,l,r;
	long long lazy,sum;
}segTree[maxn<<2][3];
void build (int i,int l,int r,int x) {
	segTree[i][x].l=l;
	segTree[i][x].r=r;
	if (l==r) {
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid,x);
	build(i<<1|1,mid+1,r,x);
}
void spread (int i,int x) {
	if (segTree[i][x].lazy) {
		segTree[i<<1][x].sum+=segTree[i][x].lazy;
		segTree[i<<1][x].lazy+=segTree[i][x].lazy;
		segTree[i<<1|1][x].sum+=segTree[i][x].lazy;
		segTree[i<<1|1][x].lazy+=segTree[i][x].lazy;
		segTree[i][x].lazy=0;
	}
}
void up (int i,int l,int r,long long v,int x) {
	if (segTree[i][x].l>=l&&segTree[i][x].r<=r) {
		segTree[i][x].sum+=v;
		segTree[i][x].lazy+=v;
		return;
	}
	spread(i,x);
	int mid=(segTree[i][x].l+segTree[i][x].r)>>1;
	if (l<=mid) up(i<<1,l,r,v,x);
	if (r>mid) up(i<<1|1,l,r,v,x);
	
	segTree[i][x].sum=max(segTree[i<<1][x].sum,segTree[i<<1|1][x].sum);
}
long long query (int i,int l,int r,int x) {
	if (l>r) return 0;
	if (segTree[i][x].l>=l&&segTree[i][x].r<=r) {
		return segTree[i][x].sum;
	}
	spread(i,x);
	int mid=(segTree[i][x].l+segTree[i][x].r)>>1;
	long long ans=-1e18;
	if (l<=mid) ans=max(ans,query(i<<1,l,r,x));
	if (r>mid) ans=max(ans,query(i<<1|1,l,r,x));
	return ans;
}
long long sum[maxn];
long long f[maxn][2];
int main () {
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",a+i),sum[i]=sum[i-1]+a[i],f[i][0]=f[i][1]=-1e18;
	//考虑DP
	//f(i,0)表示最后一段区间大于k的最优解
	//f(i,1)表示最后一段区间小于等于k的最优解
	//f(i,0)=max(f(i,0),[j=k~i-k-1]f(j,0)+sum(i)-sum(j)
	//f(i,0)=max(f(i,0),[j=1~i-k-1]f(j,1)+sum(i)-sum(j)
	//f(i,1)=max(f(i,1),[j=i-k~i-1]f(j,0)+sum(i)*2-sum(j)*2
	//f(i,1)=max(f(i,1),[j=i-k~i-1]f(j,1)+sum(i)-sum(j)
	//把和j有关的项合并,就可以用线段树维护了,比赛的时候怎么就没想到呢
	//开3颗线段树
	//第一棵维护f(i,0)-sum(i)
	//第二棵维护f(i,1)-sum(j)
	//第三颗维护f(i,0)-sum(j)*2
	build(1,1,n,0);
	build(1,1,n,1);
	build(1,1,n,2);
	for (int i=1;i<=n;i++) {
		if (i<=k) {
			f[i][1]=sum[i];
			up(1,i,i,f[i][1]-sum[i],1);
			up(1,i,i,f[i][0]-sum[i],0);
			up(1,i,i,f[i][0]-sum[i]*2,2);
		}
		else {
			 f[i][0]=max(f[i][0],query(1,1,i-k-1,0)+sum[i]);
			 f[i][0]=max(f[i][0],query(1,1,i-k-1,1)+sum[i]);
			 f[i][1]=max(f[i][1],query(1,i-k,i-1,2)+sum[i]*2);
			 f[i][1]=max(f[i][1],query(1,i-k,i-1,1)+sum[i]);
 			
			 up(1,i,i,f[i][0]-sum[i],0);
			 up(1,i,i,f[i][1]-sum[i],1);
			 up(1,i,i,f[i][0]-sum[i]*2,2);
		}
	}
	printf("%lld
",max(f[n][0],f[n][1]));
}
原文地址:https://www.cnblogs.com/zhanglichen/p/14543858.html