一道基础构造练习题

\(4n\) 个物品,第 \(i\) 个物品的重量为 \(i\)

物品分为 \(n\) 种,每种物品恰好有 \(4\) 个。

你需要将所有物品划分成两个集合,使其满足下列条件:

  • 两个集合中物品的总重量相等。

  • 对于每种物品,在两个集合中的数量相等。

\(1 \leq n \leq 500000\)


  • 问题必然有解,且物品 \(i\) 和物品 \(4n+1-i\) 必然在同一个集合内。

我们只要构造一个满足要求的方案,就能解决问题了。

考虑此时对于每种物品建立一个点,则会产生 \(n\) 个点的图。

对于物品 \(i\) 和物品 \(4n+1-i\),记其种类分别为 \(a\)\(b\),则在 \(a\)\(b\) 之间连边。

显然,在连出的新图,有 \(n\) 个点和 \(2n\) 条边,每个点的度数都是 \(4\)

我们考虑找到新图的每个连通块,容易发现其具有如下性质:

  • 每个连通块都存在欧拉回路,且欧拉回路的边数为偶数。

这个性质显然,因为连通块内每个点的度数都是偶数,且边数也是偶数。

接下来我们考虑如下构造:

  • 找到每个连通块的欧拉回路,将奇数边加入一个集合,偶数边加入另一个集合。

显然,如此构造后的两个集合总重量相等,并且一个点恰好会经过两条奇数边和两条偶数边。

也就是说,我们找到了构造方案。

//Ad astra per aspera
#include<iostream>
#include<vector>
#include<cstdio>
using namespace std;
int a[2000010],ans[2000010];
bool vis[500010],vis_edge[1000010];
struct Node{
	int id,v;
};
vector<Node> G[500010];
int cur[500010],idu[1000010],idv[1000010];
int edge_tot;
void dfs(int u,int fa){
	vis[u]=true;
	while(cur[u]<G[u].size()){
		int id=G[u][cur[u]].id,v=G[u][cur[u]].v;
		if(!vis_edge[id]){
			vis_edge[id]=true;
			dfs(v,id);
		}
		cur[u]++;
	}
	if(fa){
		edge_tot++;
		if(edge_tot&1){
			ans[idu[fa]]=1;
			ans[idv[fa]]=1;
		}
	}
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=4*n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=2*n;i++){
		idu[i]=i;
		idv[i]=4*n+1-i;
		int id=i,u=a[i],v=a[4*n+1-i];
		G[u].push_back((Node){id,v});
		G[v].push_back((Node){id,u});
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			dfs(i,0);
		}
	}
	printf("YES\n");
	for(int i=1;i<=4*n;i++){
		printf("%d ",ans[i]);
	}
	return 0;
}
posted @ 2026-01-13 15:18  Oken喵~  阅读(3)  评论(0)    收藏  举报