CF1408H Rainbow Triples
一、题目
当你 \(\tt Wa\) 了十几发之后,评测机都会嘲笑你,\(\tt wdnmd\),以后还是要写注释以免写错关键细节:
二、解法
直接考虑怎么建网络流模型,但是这题是两个点决定一个点
(两个 \(0\) "匹配"中间一个权值),这个关系不太好建。考虑拆分,首先观察到我们按 \(0\) 数量平均分成两段 \(L,R\),那么显然不会出现两段内部匹配一个权值,否则我们可以通过调整使之跨过中线,并且这样更优。
那么可以转成一个点决定一个点
,也就是我们只和 \(L,R\) 中的点匹配,然后和 \(0\) 的个数除 \(2\) 取 \(\min\) 即可。设 \(l_x\) 为颜色 \(x\) 出现在 \(L\) 最右端的位置,\(r_x\) 为颜色 \(x\) 出现在 \(R\) 最左端的位置:
可以手算最小割,发现可能被割的边只可能是 源点与权值的边 和 零点与汇点的边。可以套路地枚举一些东西,我们枚举左边断掉的零点前缀,维护所有右边断掉的零点后缀的答案。
考虑对于对于一种权值,如果当前左边和汇点不连通,那么考虑右边的一段前缀就不需要割掉这个权值了,所以可以拿一棵线段树来维护,只需要区间修改和全局查询,时间复杂度 \(O(n\log n)\)
三、总结
网络流中我们常常表示单点,单点对单点的关系,而难以简单地表达多点之间的一种关系。虽然有时候可以利用图上的其他意义(比如路径),但如果要求简单建图,我们这时候可以考虑拆分成单点和单点之间的关系。
手算最小割常常和贪心法、枚举法、简单数据结构挂钩,很多题的计算方法都类似。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 500005;
const int inf = 0x3f3f3f3f;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,k,a[M],ty[M],pz[M],lc[M],rc[M];
int nxt[M],b[M],mi[4*M],fl[4*M];
void down(int i)
{
if(!fl[i]) return ;
fl[i<<1]+=fl[i];mi[i<<1]+=fl[i];
fl[i<<1|1]+=fl[i];mi[i<<1|1]+=fl[i];
fl[i]=0;
}
void build(int i,int l,int r)
{
fl[i]=0;
if(l==r) {mi[i]=b[l];return ;}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
mi[i]=min(mi[i<<1],mi[i<<1|1]);
}
void add(int i,int l,int r,int L,int R)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {mi[i]--;fl[i]--;return ;}
int mid=(l+r)>>1;down(i);
add(i<<1,l,mid,L,R);
add(i<<1|1,mid+1,r,L,R);
mi[i]=min(mi[i<<1],mi[i<<1|1]);
}
void work()
{
n=read();
for(int i=1;i<=n;i++)
lc[i]=rc[i]=-1,b[i]=inf;
for(int i=1;i<=n;i++)
a[i]=read(),pz[i]=pz[i-1]+!a[i];
m=pz[n];k=0;nxt[n+1]=n+1;
for(int i=1;i<=n;i++)
{
ty[i]=pz[i]<=(m/2)?1:2;
if(a[i] && ty[i]==1) lc[a[i]]=i;
}
for(int i=n;i>=1;i--)
{
if(a[i] && ty[i]==2) rc[a[i]]=i;
if(!a[i]) nxt[i]=i;else nxt[i]=nxt[i+1];
//find the next zero-cut
}
for(int i=1;i<=n;i++)
if(lc[i]!=-1 || rc[i]!=-1) k++;
for(int i=1;i<=n;i++)
if(!a[i] && ty[i]==2) b[i]=m-pz[i]+1+k;
b[n+1]=k;build(1,1,n+1);
for(int i=1;i<=n;i++)//for every color!!!!
if(lc[i]==-1 && rc[i]!=-1)
add(1,1,n+1,1,nxt[rc[i]]);
int ans=min(m>>1,mi[1]);
for(int nw=0,i=1;i<=n;i++)
{
if(!a[i]) nw++;
if(a[i] && lc[a[i]]==i)
{
if(rc[a[i]]==-1) add(1,1,n+1,1,n+1);
else add(1,1,n+1,1,nxt[rc[a[i]]]);
}
ans=min(ans,nw+mi[1]);
}
printf("%d\n",ans);
}
signed main()
{
T=read();
while(T--) work();
}