• 题解「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;
    }
    
  • 相关阅读:
    Java基础——原码, 反码, 补码 详解
    为什么Java byte 类型的取值范围是-128~127
    JAVA 各种数值类型最大值和最小值 Int, short, char, long, float,&nbs
    JDK config
    为什么要设置Java环境变量(详解)
    什么是JAR包?
    如何用python将一个时间序列转化成有监督学习
    ImportError: numpy.core.multiarray failed to import
    搭建SDN网络——mininet
    回溯法解决最大团问题
  • 原文地址:https://www.cnblogs.com/tommy0103/p/13831849.html
Copyright © 2020-2023  润新知