P10683 [COTS 2024] 划分 Particija

思路

转化一下题意,\(a_i\)\(b_i\) 恰有一个限制被满足,变成在一个二分图上有一些边 \((a_i,b_i)\),求最小点覆盖。
\(k>0\) 时可以更改一条边的一个端点,使得最大或最小化最小点覆盖。

不难发现整个图是由若干二分图联通块组成的。

\(k=0\) 时,对每个二分图求最小点覆盖,直接 \(O(n)\) 黑白染色。

\(k=1\) 时,因为有 \((\min(x_1,y_1)+\min(x_2,y_2)) = \min(x_1+x_2,x_1+y_2,x_2+y_1,x_2+y_2) \le \min(x_1+x_2,y_1+y_2)\),这启发我们把一个块分成两个块才能使答案变小。
于是我们更改的点一定属于一条割边,然后我们考虑应该把这个端点挪移到哪里。

假如分成的两个连通块存在一个不是孤点,那么直接把更改的端点变成这个连通块里的点即可,这样做不会产生新的贡献。
如果分成的两个连通块都是孤点,那么更改这条边不会更优,代码实现上可以直接不管。
然后枚举每一条割边计算答案即可。

\(k=2\) 时,我们要合并两个连通块。
假如一个连通块左部点大于右部点,那么我们肯定找一个右部点大于左部点的块和它合并。每次贪心的拿右部点比左部点多的数量最多的出来匹配。
左部点数量小于右部点同理,找右部点比左部点少的数量最多的出来匹配。

所以我们枚举所有边,计算把它连给另一个连通块的贡献就行。
但是如果移动的这条边是割边,那还得额外再算分开连通块的贡献,然后再分别算分成的两个连通块和哪个连通块合并。

但是其实还有一个小问题,我们上面的不等式是基于总点数不变的。仔细阅读题面,发现我们的更改操作其实是可以新建节点的。这个问题在 \(k=1\) 的时候不会体现,因为新建节点只会让答案变大。
于是,我们还得再额外统计一下新建一个节点能产生的贡献,新建的节点必须和原节点的颜色一样。

最后记得特判 \(n=1\),此时无论如何不会存在两个连通块。

代码

怕写挂,遂对每一个包都重新写了 Tarjan 和统计答案。
\(k=2\) 含巨量分讨,慎看。

const int N = 4e5+10;
int n,a[N],b[N];
struct{
	int to,nex,from;
}edge[N<<1];
int head[N],edge_num;
inline void add(int x,int y){
	edge[++edge_num].to = y;
	edge[edge_num].nex = head[x];
	head[x] = edge_num;
	edge[edge_num].from = x;
}


// k = 0


namespace Sub0{
int cnt[2],vis[N];
inline void dfs(int now){
	cnt[now>n]++;
	vis[now] = 1;
	for(int i=head[now];i;i=edge[i].nex){
		int tto = edge[i].to;
		if(vis[tto]) continue;
		dfs(tto);
	}
}
inline void solve(int T){
	int ans = 0;
	for(int i=1;i<=n;++i) if(!vis[a[i]]){
		cnt[0] = cnt[1] = 0;
		dfs(a[i]);
		ans += Min(cnt[0],cnt[1]);
	}
	for(int i=1;i<=n;++i) if(!vis[b[i]+n]){
		cnt[0] = cnt[1] = 0;
		dfs(b[i]+n);
		ans += Min(cnt[0],cnt[1]);
	}
	printf("%d",ans);
	if(T) for(int i=1;i<=n+n;++i) vis[i] = 0;
}
}


// k = 1


namespace Sub1{
int dfn[N],low[N],dtop,is_cut[N];
int cnt[N][2],tmp[N][2];
inline void tarjan(int now,int fu,int tp){
	dfn[now] = low[now] = ++dtop;
	int flag = 0;
	cnt[tp][now>n]++;
	for(int i=head[now];i;i=edge[i].nex){
		int tto = edge[i].to;
		if(!dfn[tto]){
			tarjan(tto,now,tp);
			low[now] = Min(low[now],low[tto]);
			if(low[tto]>dfn[now]) is_cut[tto] = 1;
		}
		else{
			if(tto!=fu || flag) low[now] = Min(low[now],dfn[tto]);
			else flag = 1;
		}
	}
}
int num,ans;
inline void get_ans(int now,int tp){
	tmp[now][now>n]++;
	for(int i=head[now];i;i=edge[i].nex){
		int tto = edge[i].to;
		if(tmp[tto][0]+tmp[tto][1]) continue;
		get_ans(tto,tp);
		tmp[now][0] += tmp[tto][0];
		tmp[now][1] += tmp[tto][1];
		if(is_cut[tto]){
			ans = Min(ans,num-Min(cnt[tp][0],cnt[tp][1])+
			Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1]));
		} 
	}
}
inline void solve(int T){
	for(int i=1;i<=n;++i) if(!dfn[a[i]]){
		cnt[a[i]][0] = cnt[a[i]][1] = 0;
		tarjan(a[i],a[i],a[i]);
		num += Min(cnt[a[i]][0],cnt[a[i]][1]);
	}
	for(int i=1;i<=n;++i) if(!dfn[b[i]+n]){
		cnt[b[i]+n][0] = cnt[b[i]+n][1] = 0;
		tarjan(b[i]+n,b[i]+n,b[i]+n);
		num += Min(cnt[b[i]+n][0],cnt[b[i]+n][1]);
	}
	ans = num;
	for(int i=1;i<=n;++i) if(!(tmp[a[i]][0]+tmp[a[i]][1])) get_ans(a[i],a[i]);
	for(int i=1;i<=n;++i) if(!(tmp[b[i]+n][0]+tmp[b[i+n]][1])) get_ans(b[i]+n,b[i]+n);
	printf("%d",ans);
	if(T){
		dtop = num = 0;
		for(int i=1;i<=n+n;++i){
			low[i] = dfn[i] = is_cut[i] = 0;
			cnt[i][0] = cnt[i][1] = 0;
			tmp[i][0] = tmp[i][1] = 0;
		}
	}
}
}


// k = 2


namespace Sub2{
int dfn[N],low[N],dtop,is_cut[N];
int cnt[N][2],tmp[N][2];
inline void tarjan(int now,int fu,int tp){
	dfn[now] = low[now] = ++dtop;
	int flag = 0;
	cnt[tp][now>n]++;
	for(int i=head[now];i;i=edge[i].nex){
		int tto = edge[i].to;
		if(!dfn[tto]){
			tarjan(tto,now,tp);
			low[now] = Min(low[now],low[tto]);
			if(low[tto]>dfn[now]) is_cut[tto] = 1;
		}
		else{
			if(tto!=fu || flag) low[now] = Min(low[now],dfn[tto]);
			else flag = 1;
		}
	}
}
int minn[2],maxn[2],num,ans;
inline void get_ans(int now,int tp){
	tmp[now][now>n]++;
	for(int i=head[now];i;i=edge[i].nex){
		int tto = edge[i].to;
		if(tmp[tto][0]+tmp[tto][1]) continue;
		get_ans(tto,tp);
		tmp[now][0] += tmp[tto][0];
		tmp[now][1] += tmp[tto][1];
		if(is_cut[tto]){
			if(cnt[tp][0]-tmp[tto][0]>cnt[tp][1]-tmp[tto][1]){
				ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(minn[0],minn[1])+
					Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0]+minn[0],cnt[tp][1]-tmp[tto][1]+minn[1]));
			}
			else{
				ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(maxn[0],maxn[1])+
					Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0]+maxn[0],cnt[tp][1]-tmp[tto][1]+maxn[1]));
			}
			if(tmp[tto][0]>tmp[tto][1]){
				ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(minn[0],minn[1])+
					Min(tmp[tto][0]+minn[0],tmp[tto][1]+minn[1])+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1]));
			}
			else{
				ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(maxn[0],maxn[1])+
					Min(tmp[tto][0]+maxn[0],tmp[tto][1]+maxn[1])+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1]));
			}
			ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+
				Min(tmp[tto][0]+(tto>n),tmp[tto][1]+(tto<=n))+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1]));
			ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+
				Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0]+(now>n),cnt[tp][1]-tmp[tto][1]+(now<=n)));
		}
		else{
			if(cnt[tp][0]>cnt[tp][1]){
				ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(minn[0],minn[1])+
					Min(cnt[tp][0]+minn[0],cnt[tp][1]+minn[1]));
			}
			else{
				ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(maxn[0],maxn[1])+
					Min(cnt[tp][0]+maxn[0],cnt[tp][1]+maxn[1]));
			}
			ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+
				Min(cnt[tp][0]+1,cnt[tp][1]));
			ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+
				Min(cnt[tp][0],cnt[tp][1]+1));
		}
	}
}
inline void solve(int T){
	minn[0] = 0,minn[1] = 0;
	maxn[0] = 0,maxn[1] = 0;
	for(int i=1;i<=n;++i) if(!dfn[a[i]]){
		cnt[a[i]][0] = cnt[a[i]][1] = 0;
		tarjan(a[i],a[i],a[i]);
		num += Min(cnt[a[i]][0],cnt[a[i]][1]);
		if(minn[0]-minn[1]>cnt[a[i]][0]-cnt[a[i]][1]){
			minn[0] = cnt[a[i]][0];
			minn[1] = cnt[a[i]][1];
		}
		if(maxn[0]-maxn[1]<cnt[a[i]][0]-cnt[a[i]][1]){
			maxn[0] = cnt[a[i]][0];
			maxn[1] = cnt[a[i]][1];
		}
	}
	for(int i=1;i<=n;++i) if(!dfn[b[i]+n]){
		cnt[b[i]+n][0] = cnt[b[i]+n][1] = 0;
		tarjan(b[i]+n,b[i]+n,b[i]+n);
		num += Min(cnt[b[i]+n][0],cnt[b[i]+n][1]);
		if(minn[0]-minn[1]>cnt[b[i]+n][0]-cnt[b[i]+n][1]){
			minn[0] = cnt[b[i]+n][0];
			minn[1] = cnt[b[i]+n][1];
		}
		if(maxn[0]-maxn[1]<cnt[b[i]+n][0]-cnt[b[i]+n][1]){
			maxn[0] = cnt[b[i]+n][0];
			maxn[1] = cnt[b[i]+n][1];
		}
	}
	ans = num;
	for(int i=1;i<=n;++i) if(!(tmp[a[i]][0]+tmp[a[i]][1])) get_ans(a[i],a[i]);
	for(int i=1;i<=n;++i) if(!(tmp[b[i]+n][0]+tmp[b[i+n]][1])) get_ans(b[i]+n,b[i]+n);
	printf("%d",ans);
	if(T){
		dtop = num = 0;
		for(int i=1;i<=n+n;++i){
			low[i] = dfn[i] = is_cut[i] = 0;
			cnt[i][0] = cnt[i][1] = 0;
			tmp[i][0] = tmp[i][1] = 0;
		}
	}
}
}


// main


int main(){
	// freopen("in.in","r",stdin); 
	// freopen("out.out","w",stdout);

	int T,ID; read(T,ID);
	while(T--){
		read(n);
		for(int i=1;i<=n;++i) read(a[i]);
		for(int i=1;i<=n;++i) read(b[i]);
		if(n==1){
			puts("1");
			continue;
		}
		for(int i=1;i<=n;++i){
			add(a[i],b[i]+n);
			add(b[i]+n,a[i]);
		}
		if(ID==0) Sub0 :: solve(T);
		else if(ID==1) Sub1 :: solve(T);
		else Sub2 :: solve(T);
		if(T){
			for(int i=1;i<=n+n;++i) head[i] = 0;
			edge_num = 0;
			putchar('\n');
		}
	}

	// fclose(stdin);
	// fclose(stdout);
	return 0;
}
posted @ 2025-11-22 16:50  Tmbcan  阅读(9)  评论(0)    收藏  举报