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

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

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

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

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

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

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

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

【参考代码】

#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值