题目大意
题解
好题
设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;
}