【线段树+单调栈维护DP】LOJ2773 「ROI 2017 Day 2」学习轨迹

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/89526982

【题目】
LOJ
有两所学校,第一所学校有 n n 门课程,编号分别是 a 1 , , a n a_1,\dots ,a_n ,课程质量 x i x_i 。第二所学校有 m m 门课程,编号分别是 b 1 , , b m b_1,\dots,b_m ,课程质量 y i y_i 。两所学校开设课程编号可能相同。
现在可以在分别学校学习连续一段课程,比如 a l , a l + 1 , , a r a_{l},a_{l+1},\dots, a_{r} ,但需要满足在两所学校选择课程编号不同。求最大课程质量和。

n , m 5 × 1 0 5 n,m\leq 5\times 10^5

【解题思路】
如果只上一所学校课程,显然会选择这所学校所有课程,因此至少有一所学校选择的课程权值超过这所学校总权值一半。

假设现在第一所学校一定要超过一半,那么设第一所学校第一次前缀和超过总权值一半的位置为 p p ,则这个位置一定要被选择。

那么答案就是在第二所学校的课程中旋任意一段,然后将重复的课程在第一所学校的课表中标记出来,从 p p 开始往两边尽可能拓展。

枚举第二所学校所选择的右端点,那么每次至多一个位置不能被选择,不妨用两个单调栈维护当前每个 l l 到当前的 r r 的区间,在第一所学校选择的左右端点分别是什么,可以用线段树维护被影响的区间贡献。

复杂度 O ( n log n ) O(n\log n)

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=5e5+10;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

namespace DreamLolita
{
	int n,m,topl,topr,p1,p2,p3,p4,fg;
	int a[N],b[N],c[N<<1],sl[N],sr[N];
	ll ans,suma[N],sumb[N];
	struct Segment
	{
		#define ls (x<<1)
		#define rs (x<<1|1)
		ll mx[N<<2],tar[N<<2];
		void build(int x,int l,int r)
		{
			tar[x]=0;
			if(l==r){mx[x]=suma[n]-sumb[l-1];return;}
			int mid=(l+r)>>1;
			build(ls,l,mid);build(rs,mid+1,r);
			mx[x]=max(mx[ls],mx[rs])+tar[x];
		}
		void update(int x,int l,int r,int L,int R,ll v)
		{
			if(L<=l && r<=R){mx[x]+=v;tar[x]+=v;return;}
			int mid=(l+r)>>1;
			if(L<=mid) update(ls,l,mid,L,R,v);
			if(R>mid) update(rs,mid+1,r,L,R,v);
			mx[x]=max(mx[ls],mx[rs])+tar[x];
		}
		int query(int x,int l,int r)
		{
			if(l==r) return l;
			int mid=(l+r)>>1;
			if(mx[x]==mx[ls]+tar[x]) return query(ls,l,mid); 
			else return query(rs,mid+1,r);
		}
		#undef ls
		#undef rs
	}T;
	void updateans(ll v,int a,int b,int c,int d)
	{
		if(fg) swap(a,c),swap(b,d);
		if(v>ans) ans=v,p1=a,p2=b,p3=c,p4=d;
	}
	void solve()
	{
		int mid=lower_bound(suma+1,suma+n+1,(suma[n]+1)/2)-suma;
		T.build(1,1,m);topl=topr=0;
		for(int i=1;i<=m;++i)
		{
			if(b[i])
			{
				if(b[i]<=mid)
				{
					while(topl && b[i]>b[sl[topl]]) T.update(1,1,m,sl[topl-1]+1,sl[topl],suma[b[sl[topl]]]),--topl;
					T.update(1,1,m,sl[topl]+1,i,-suma[b[i]]);sl[++topl]=i;
				}
				else
				{
					while(topr && b[i]<b[sr[topr]]) T.update(1,1,m,sr[topr-1]+1,sr[topr],suma[n]-suma[b[sr[topr]]-1]),--topr;
					T.update(1,1,m,sr[topr]+1,i,suma[b[i]-1]-suma[n]);sr[++topr]=i;
				}
			}
			int j=T.query(1,1,m);
			int pl=lower_bound(sl+1,sl+topl+1,j)-sl,pr=lower_bound(sr+1,sr+topr+1,j)-sr;
			pl=(pl<=topl?b[sl[pl]]+1:1);pr=(pr<=topr?b[sr[pr]]-1:n);
			updateans(sumb[i]+T.mx[1],pl,pr,j,i);
		}
	}
	void solution()
	{
		n=read();m=read();
		for(int i=1;i<=n;++i) a[i]=read();
		for(int i=1;i<=n;++i) suma[i]=suma[i-1]+read();
		for(int i=1;i<=m;++i) c[read()]=i;
		for(int i=1;i<=m;++i) sumb[i]=sumb[i-1]+read();
		for(int i=1;i<=n;++i) a[i]=c[a[i]],b[a[i]]=i;
		updateans(suma[n],1,n,0,0);updateans(sumb[m],0,0,1,m);
		solve();swap(n,m);swap(a,b);swap(suma,sumb);fg=1;solve();
		printf("%lld\n%d %d\n%d %d\n",ans,p1,p2,p3,p4);
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("LOJ2773.in","r",stdin);
	freopen("LOJ2773.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/89526982
ROI