一道基础构造练习题
有 \(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;
}

浙公网安备 33010602011771号