Loading

CF1408H Rainbow Triples

传送门

Solution

题意已经很清楚了。

考虑这样一个三元组的位置,首先是 \(b\) 要选在非 \(0\) 位,\(a,c\) 分别要在它的左右两边。那么有一个很明显的答案上界就是 \(\dfrac{n_0}{2}\),其中 \(n_0\) 表示序列中 \(0\) 的个数。

然后来考虑这个问题第一困难的地方,是你需要考虑每个元素同时两边的 \(0\) 的个数,这使得我们非常头痛,于是我们依据 \(\dfrac{n_0}{2}\) 把序列分成两部分 \(L,R\)。这样 \(L\) 就只需要考虑它左边的 \(0\) 是否足够,\(R\) 只需要考虑它右边的 \(0\) 是否足够,另一边不需要考虑。因为如果另一边也不够那么考虑的这一边必然是不够的。

第二困难的地方是想到网络流建模。首先考虑每个 \(0\) 只能被用 \(1\) 次,所以我们从每个 \(0\) 向终点连一条容量为 \(1\) 的边。注意到题目中还有颜色的限制,于是我们从起点向各个颜色连一条容量为 \(1\) 的边从而保证了每种颜色都不重复。然后颜色向对应节点连边……不,不需要,我们发现这条边只需要给全村的希望就可以了,也就是最有可能被选出的点,即左边最右的点和右边最左的点。然后各个节点向其左右的 \(0\) 分别连边……哦会爆炸,于是我们在左边后一个向前一个连一条边,右边后一个向前一个连一条边,容量正无穷,替代了一个一个连边。建完大概是长这样的:

然后最大流就是答案。

显然跑不过去,于是 new trick:模拟网络流。一般考虑最大流=最小割,然后枚举最小割。由于别的边都是正无穷的,而删中间的 \(1\) 边必定劣于割源点到颜色的边,所以我们只可能割颜色到源点的边和 \(0\) 到汇点的边。如果一个颜色被选中不割了,那么这个颜色所连到的所有 \(0\) 都要割掉,即其前缀和一些后缀。于是我们枚举割掉的前缀的 \(0\) 的数量,然后后缀 \(0\) 的数量用线段树维护,那要割的颜色边数量就确定了。

实现方法是采用线段树,下标为 \(i\) 的节点存储后缀割去 \(i\)\(0\) 边所要割掉的颜色边加上后缀 \(0\) 边数量。

  1. 枚举 \(L\) 中的一个非 \(0\) 元素,它前面的 \(0\) 都割掉,那么当 \(R\) 中割去的 \(0\) 边也大于其后缀时,当前元素都不需要断掉颜色边,所以线段树上区间减一,表示这条边不需要断掉了。
  2. 在此之前,如果一种颜色没有出现在 \(L\) 中,那么在 \(R\) 中割掉一定的后缀数量也会使得这条颜色边不需要割。
  3. 枚举的时候,如果一种元素没有出现在 \(R\) 中,那么在 \(R\) 中全局减一,因为这个颜色无论后面怎么割都不需要割了。

然后每次取最小值就是答案了。

Code

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb push_back
using namespace std;
const int MAXN=5e5+10;
struct Tree{int l,r,mn,inc;}tr[MAXN<<2];
#define ls i<<1
#define rs i<<1|1
int Col;
void pushup(int i){tr[i].mn=min(tr[ls].mn,tr[rs].mn);}
void build(int i,int l,int r){
	tr[i].l=l;tr[i].r=r;tr[i].inc=0;
	if(l==r){tr[i].mn=Col+l;return;}
	int mid=l+r>>1;build(ls,l,mid);build(rs,mid+1,r);
	pushup(i);
}
void add(int i,int v){tr[i].mn+=v;tr[i].inc+=v;}
void pushdown(int i){
	if(!tr[i].inc) return;
	add(ls,tr[i].inc);add(rs,tr[i].inc);
	tr[i].inc=0;
}
void upd(int i,int l,int r,int v){
	if(tr[i].l==l&&tr[i].r==r){add(i,v);return;}
	pushdown(i);int mid=tr[i].l+tr[i].r>>1;
	if(r<=mid) upd(ls,l,r,v);else if(l>mid) upd(rs,l,r,v);
	else upd(ls,l,mid,v),upd(rs,mid+1,r,v);
	pushup(i);
}
int ask(int i,int x){
	if(tr[i].l==tr[i].r) return tr[i].mn;
	pushdown(i);int mid=tr[i].l+tr[i].r>>1;
	if(x<=mid) return ask(ls,x);else return ask(rs,x);
}
int p[MAXN],pre[MAXN],Cl[MAXN],Cr[MAXN];
vector<int> L,R;
void init(int n){
	L.clear();R.clear();Col=0;
	for(int i=1;i<=n;i++) Cl[i]=Cr[i]=0;
}
void solve(){
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&p[i]),pre[i]=pre[i-1]+(p[i]==0);
	int n0=pre[n]/2;
	for(int i=1;i<=n;i++){
		if(!p[i]) continue;
		if(pre[i]<=n0) L.pb(i);else R.pb(i);
	}
	for(int i=0;i<(int)L.size();i++) Cl[p[L[i]]]=L[i];
	for(int i=(int)R.size()-1;i>=0;i--) Cr[p[R[i]]]=R[i];
	for(int i=1;i<=n;i++) Col+=(Cl[i]||Cr[i]);
	int tot=pre[n]-n0;build(1,0,tot);
//	cerr<<"Seg Ele: ";for(int i=0;i<=tot;i++) cerr<<ask(1,i)<<' ';cerr<<'\n';
	for(int i=1;i<=n;i++) if(!Cl[i]&&Cr[i]) upd(1,pre[n]-pre[Cr[i]-1],tot,-1);
//	cerr<<"Seg Ele: ";for(int i=0;i<=tot;i++) cerr<<ask(1,i)<<' ';cerr<<'\n';
	int ans=min(tr[1].mn,n0);
	for(int i=1;i<=n&&pre[i]<=n0;i++){
		if(Cl[p[i]]^i) continue;
		if(Cr[p[i]]) upd(1,pre[n]-pre[Cr[p[i]]-1],tot,-1);
		else upd(1,0,tot,-1);
//		cerr<<"After "<<i<<" 0s\n";
//		cerr<<"Seg Ele: ";for(int j=0;j<=tot;j++) cerr<<ask(1,j)<<' ';cerr<<'\n';
		ans=min(ans,tr[1].mn+pre[i]);
	}
	printf("%d\n",ans);
	init(n);
}
int main()
{
	int T;
	for(scanf("%d",&T);T--;)
		solve();
	return 0;
}
posted @ 2022-01-22 09:38  ZCETHAN  阅读(55)  评论(0编辑  收藏  举报