题解「CF1408H Rainbow Triples」

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

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

可以发现 \(L\) 集合中的数,只会考虑在左边 \(0\) 的取值,\(R\) 集合中的数,只会考虑在右边 \(0\) 的取值。因为对于 \(L\) 集合中任意的数左边的数 \(0\) 的不同取值不超过 \(\left\lfloor\frac{n}{2}\right\rfloor\),而右边的 \(0\) 一定不小于 \(\left\lfloor\frac{n}{2}\right\rfloor\),根据答案上界,无需考虑右边这部分 \(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\n",ans);
	}
	return 0;
}
posted @ 2020-10-17 17:12  tommymio  阅读(134)  评论(0)    收藏  举报