• CF1408H. Rainbow Triples


    题目大意

    题解

    好题

    设0的个数为z,一个显然的结论是答案上界为z/2

    以第z/2个0为分界划开,左边的称为L右边的称为R,那么L中右侧和R中左侧的0个数>=z/2

    可以发现这样转化之后一个点只需要考虑在其所在集合的连边,即L集考虑向左的边R集考虑向右的边

    因为总数<=z/2而任意一边的0个数>=z/2,每个数只会对某一边贡献一次,所以一定有解

    同一种颜色只需要保留L集最右点和R集最左点,然后显然是网络流

    L集中i->i-1,R集中i->i+1,一个颜色向两个集合中的两个点(x,y)连边,S向颜色连边,0向T连边,因为n是5e5所以应该跑不过

    考虑用最小割求最大流,如果一种颜色没有被割那么其向两个集合连边的前缀x和后缀y的0都要被割,所以割掉的是一段前缀0一段后缀0和一些颜色

    那么枚举割掉的前缀长度,把(x,y)按x排序扫描线+线段树维护后缀的答案即可

    code

    #include <bits/stdc++.h>
    #define fo(a,b,c) for (a=b; a<=c; a++)
    #define fd(a,b,c) for (a=b; a>=c; a--)
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    #define ll long long
    //#define file
    using namespace std;
    
    struct type{int x,y;} b[500001];
    int tr[2000011],Tr[2000011],a[500001],T,n,N,i,j,k,l,I,sum,ans;
    
    void down(int t,int len)
    {
    	if (Tr[t])
    	{
    		if (len>1)
    		Tr[t*2]+=Tr[t],Tr[t*2+1]+=Tr[t];
    		tr[t]+=Tr[t],Tr[t]=0;
    	}
    }
    void up(int t) {tr[t]=min(tr[t*2]+Tr[t*2],tr[t*2+1]+Tr[t*2+1]);}
    void change(int t,int l,int r,int x,int y,int s)
    {
    	int mid=(l+r)/2;
    	if (x<=l && r<=y) {Tr[t]+=s;down(t,r-l+1);return;}
    	
    	if (x<=mid) change(t*2,l,mid,x,y,s);
    	if (mid<y) change(t*2+1,mid+1,r,x,y,s);
    	up(t);
    }
    
    bool cmp(type a,type b) {return a.x<b.x;}
    
    int main()
    {
    	#ifdef file
    	freopen("CF1408H.in","r",stdin);
    	freopen("b.out","w",stdout);
    	#endif
    	
    	scanf("%d",&T);
    	for (;T;--T)
    	{
    		scanf("%d",&n),sum=0,N=n+1;
    		memset(tr,0,(N*4+1)*4);
    		memset(Tr,0,(N*4+1)*4);
    		fo(i,1,n) scanf("%d",&a[i]),sum+=!a[i];
    		
    		if (a[n]==2)
    		n=n;
    		
    		if (sum<2) {printf("0
    ");continue;}
    		l=0;
    		fo(I,1,n) if (!a[I]) {++l;if (l==sum/2) break;}
    		
    		fo(i,1,n) b[i]={0,N};
    		fo(i,1,I) b[a[i]].x=i;
    		fd(i,n,I+1) b[a[i]].y=i;
    		
    		l=0;
    		fd(i,n,1) l+=!a[i],change(1,1,N,i,i,l);
    		Tr[1]+=n,down(1,N);
    		sort(b+1,b+n+1,cmp);
    		ans=2147483647,j=1,l=0;
    		fo(i,1,n)
    		{
    			while (j<=n && i>=b[j].x)
    			change(1,1,N,1,b[j].y,-1),++j;
    			ans=min(ans,l+(tr[1]+Tr[1]));
    			l+=!a[i];
    		}
    		
    		printf("%d
    ",min(ans,sum/2));
    	}
    	
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 相关阅读:
    汉字词组换行
    C#中获取Excel文件的第一个表名
    SQL查找某一条记录的方法
    C#数据库连接字符大全
    整理的asp.net资料!(不得不收藏)
    母版页的优点,及母版页与内容页中相互访问方法
    13范式
    使用 Jackson 树连接线形状
    word2007,取消显示回车符
    三张表之间相互的多对多关系
  • 原文地址:https://www.cnblogs.com/gmh77/p/13823304.html
Copyright © 2020-2023  润新知