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\n");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\n",min(ans,sum/2));
	}
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}
posted @ 2020-10-15 21:33  gmh77  阅读(121)  评论(0编辑  收藏  举报