题解「CF1408H Rainbow Triples」

一道模拟网络流的好题。没想到是 ( ext{Codeforces 3300*})/jk

对于这种看上去非常奇怪的题目,先转化模型。将整个序列 (a) 中的位置 (1sim n) 划分为两个集合 (L,R),其中 (L) 集合中的数左边的 (0) 个数不超过 (leftlfloorfrac{n}{2} ight floor)(R) 集合中的数在序列 (a) 中左边的 (0) 个数大于 (leftlfloorfrac{n}{2} ight floor)。为什么想到用 (leftlfloorfrac{n}{2} ight floor) 来划分呢?因为答案有个非常显然的上界 (leftlfloorfrac{n}{2} ight floor)

可以发现 (L) 集合中的数,只会考虑在左边 (0) 的取值,(R) 集合中的数,只会考虑在右边 (0) 的取值。因为对于 (L) 集合中任意的数左边的数 (0) 的不同取值不超过 (leftlfloorfrac{n}{2} ight floor),而右边的 (0) 一定不小于 (leftlfloorfrac{n}{2} ight floor),根据答案上界,无需考虑右边这部分 (0) 而只需考虑左边的 (0) 的取值。(R) 集合同理。

如果 (L/R) 集合中出现相同取值怎么办?在 (L) 集合中,对于相同值,我们只会保留最靠右的位置。而在 (R) 集合中,对于相同值,我们只会保留最靠左的位置。因为,如果不是这样,对方案进行一些微调达到这样,不会更劣,可以自行证明。或者,可以感性理解 (L) 集合中最靠右的位置,左边的 (0) 的取值更多,(R) 集合同理。

那么,我们现在成功的转化了问题,对于每个值 (x),只需考虑其在 (L) 集合中的位置 (l_x)(R) 集合中的位置 (r_x)。可以使用网络流求解这个问题:将源点 (S) 与权值 (x) 连一条权值为 (1) 的边。(a_i=0)(i) 与汇点 (T) 连一条权值为 (1) 的边。权值 (x)(l_x)(r_x) 分别连一条权值为 (1) 的边。(l_x)(l_x-1) 连一条权值为 (infty) 的边,(r_x)(r_x+1) 连一条权值为 (infty) 的边。求解最大流即可。

但是 (n) 非常大,我们必须另寻他法。考虑到网络最大流 (=) 网络最小割,改为求解最小割。对于一组最小割,自然是割去一些前缀的 (0) 的边,一些权值的边,一些后缀 (0) 的边。不妨枚举割去前缀 (0) 的边的数量,使用线段树维护最小需割去的权值边与后缀 (0) 边的数量。

更为具体的说,就是枚举割去前缀 (0) 边时,减去那些不必要割去的权值边的数量,这一部分需要一个支持区间 (min),区间减量的数据结构,使用线段树维护即可。

于是时间复杂度降为 (O(n log n))

#include<cstdio>
#define ls p<<1
#define rs p<<1|1
int cnt=0;
int L[500005],R[500005],sum[500005],a[500005],ty[500005];
int mn[2000005],tag[2000005];
inline int read() {
	register int x=0,f=1;register char s=getchar();
	while(s>'9'||s<'0') {if(s=='-') f=-1;s=getchar();}
	while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
	return x*f;
}
inline int min(const int &x,const int &y) {return x<y? x:y;}
inline void build(int p,int l,int r) {
	tag[p]=0; if(l==r) {mn[p]=cnt+l; return;}
	int mid=l+r>>1; build(ls,l,mid); build(rs,mid+1,r);
	mn[p]=min(mn[ls],mn[rs]);
}
inline void spread(int p) {if(tag[p]) {mn[ls]+=tag[p]; mn[rs]+=tag[p]; tag[ls]+=tag[p]; tag[rs]+=tag[p]; tag[p]=0;}}
inline void dec(int p,int l,int r,int L,int R) {
	if(L<=l&&r<=R) {--mn[p];--tag[p];return;}
	spread(p); int mid=l+r>>1;
	if(L<=mid) dec(ls,l,mid,L,R);
	if(R>mid) dec(rs,mid+1,r,L,R);
	mn[p]=min(mn[ls],mn[rs]);
}
int main() {
	int T=read();
	while(T--) {
		int n=read(),ans=0,m,M; cnt=0;
		for(register int i=1;i<=n;++i) L[i]=R[i]=0;
		for(register int i=1;i<=n;++i) {a[i]=read();sum[i]=sum[i-1]+(!a[i]);} m=sum[n]/2;
		for(register int i=1;i<=n;++i) ty[i]=(sum[i]>m)+1;
		for(register int i=1;i<=n;++i) if(ty[i]==1&&a[i]) L[a[i]]=i;
		for(register int i=n;i>=1;--i) if(ty[i]==2&&a[i]) R[a[i]]=i;
		for(register int i=1;i<=n;++i) cnt+=(L[i]||R[i]);
		build(1,0,M=sum[n]-m);
		for(register int i=1;i<=n;++i) if(!L[i]&&R[i]) dec(1,0,M,sum[n]-sum[R[i]-1],M);
		ans=min(m,mn[1]);
		for(register int i=1;i<=n&&ty[i]==1;++i) {
			if(L[a[i]]==i) {
				if(R[a[i]]) dec(1,0,M,sum[n]-sum[R[a[i]]-1],M);
				else dec(1,0,M,0,M);//Right
				ans=min(ans,sum[i]+mn[1]);
			}
		}
		printf("%d
",ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/tommy0103/p/13831849.html