Liveddd's Blog

愛にできることはまだあるかい

CF1408H Rainbow Triples

模拟网络流的技巧,用线段树来维护。

每个点最多用一次,想到将限制转化为网络流模型。设 $cnt$ 为 $0$ 的总数,那么答案上界为 $\lfloor\frac{cnt}{2}\rfloor$。我们将所有点以 $\lfloor\frac{cnt}{2}\rfloor$ 为界分为 $L,R$ 两个点集。每个颜色只能用一次,我们从源点向每个颜色连边,再从每个每个颜色向可行位置对应的 $l/r$ 连边,最大流即为答案。

考虑类似 CF1368H2 Breadboard Capacity (hard version) 的做法,考虑模拟最大流,尝试求出其最小割。我们必然会割去 $L$ 的一段前缀与 $R$ 的后缀 的连向汇点的边,以及源点连向部分颜色的边。

直接模拟割边的过程,考虑枚举 $L$ 的前缀,用线段树维护 $R$ 的答案。设当前枚举到前缀 $i$,考虑几种情况即可:

  1. 颜色 $c$ 未在 $L$ 中出现,但在 $R$ 中出现,需要在 $R$ 中最后出现前的位置割掉。
  2. 只考虑在 $L$,颜色 $c$ 于位置 $i$ 最后出现,需要在 $R$ 中最后出现前的位置割掉。
  3. 颜色 $c$ 在 $L$ 中出现,但未在 $R$ 中出现,需要 $R$ 中所有位置都不需要割掉。

直接线段树维护即可,时间复杂度 $\mathcal O(n\log n)$。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5e5+10;
template<class T>
inline void read(T &x)
{
x=0;int f=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=~x+1;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}

struct Node
{
int l,r;
int v,tag;
};
struct Seg
{
Node tr[N<<2];
void pushup(int x){tr[x].v=min(tr[x<<1].v,tr[x<<1|1].v);}
void update(int x,int k){tr[x].v+=k,tr[x].tag+=k;}
void pushdown(int x)
{
if(!tr[x].tag)return ;
update(x<<1,tr[x].tag),update(x<<1|1,tr[x].tag);
tr[x].tag=0;
}
void build(int x,int l,int r,int k)
{
tr[x].l=l,tr[x].r=r,tr[x].v=k+l,tr[x].tag=0;
if(l==r)return ;
int mid=l+r>>1;
build(x<<1,l,mid,k),build(x<<1|1,mid+1,r,k);
}
void modify(int x,int l,int r,int k)
{
if(l<=tr[x].l&&tr[x].r<=r)return update(x,k);
pushdown(x);
int mid=tr[x].l+tr[x].r>>1;
if(l<=mid)modify(x<<1,l,r,k);
if(r>mid)modify(x<<1|1,l,r,k);
pushup(x);
}
}t;
int T,n;
int a[N],pre[N];
int pl[N],pr[N];
vector<int>L,R;

inline void solve()
{
read(n);
for(int i=1;i<=n;i++)
{
read(a[i]);
pre[i]=pre[i-1]+(a[i]==0);
}
int lim=(pre[n]>>1);
int pos=1;
for(;pos<=n;pos++)
{
if(!a[pos])continue;
if(pre[pos]>lim)break;
L.push_back(pos);
}
for(int i=n;i>=pos;i--)if(a[i])R.push_back(i);
for(auto i:L)pl[a[i]]=i;
for(auto i:R)pr[a[i]]=i;
int totc=0;
for(int i=1;i<=n;i++)totc+=(pl[i]||pr[i]);
int m=pre[n]-lim;
t.build(1,0,m,totc);
for(int i=1;i<=n;i++)
if(!pl[i]&&pr[i])
t.modify(1,pre[n]-pre[pr[i]-1],m,-1);
int ans=min(t.tr[1].v,lim);
for(int i=1;i<=n;i++)
{
if(pl[a[i]]!=i)continue;
if(pr[a[i]])t.modify(1,pre[n]-pre[pr[a[i]]-1],m,-1);
else t.modify(1,0,m,-1);
ans=min(ans,pre[i]+t.tr[1].v);
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)pl[i]=pr[i]=pre[i]=0;
L.clear(),R.clear();
}
int main()
{
read(T);
while(T--)solve();
return 0;
}