【Atcoder】AtCoder Grand Contest 059 B - Arrange Your Balls | 图论、构造
题目链接
B - Arrange Your Balls (atcoder.jp)
题目
题目大意
有 个球,第 个球的颜色是 。把 个球排成一个环,如果相邻两个球的颜色不同,那么他们将会构成一个二元组 ,最小化二元组的种类数,输出排列方案。
和 属于一个种类。
思路
假设有 种不同的颜色。
容易想到可以把所有颜色相同的球放在一起,将所有球排成一个环。则每种颜色都会参与两个二元组的形成,答案为 。
是否存在更小的答案呢?
想象我们构建一棵有 个节点的树,每个节点代表一种颜色,我们对这棵树进行 DFS,每经过一条树边,我们就输出边指向的点的颜色。这样每个二元组都会与一条树边进行对应。
观察示例我们发现,用建树的方法来构建环时,树边一共被经过 次,且每种颜色的的球至少出现了度数次。(如果建出的树某点的度数小于对应颜色球的数量,只需要连续输出多次即可。)
具体来说,令 表示第 种颜色的球的个数,只有在满足条件
时,才能建出树来。
怎么建树呢?首先将所有点都压进待合并的集合 中,进行 次下述操作:
- 找到 中度数最小的点 。
- 找到 另一点 ,满足:若 中点的数量大于 , 的度数应大于 。
- 在 到 之间建边。
- 将点 的度数减小 。
- 从集合 中删除点 。
建出树后我们对树进行一遍 DFS,输出答案即可。
代码
#include <stdio.h>
#include <queue>
#include <algorithm>
#include <vector>
using namespace std;
const int N=200001;
int n,m,k,tot;
using LL=long long;
int a[N],cnt[N];
vector<int> e[N];
struct asdf{
int col,num;
bool operator < (const asdf a) const
{
return num<a.num;
}
}q[N];
void dfs(int u,int fa)
{
for (auto v:e[u])
{
if (v==fa) continue;
printf("%d ",v);
cnt[v]--;
dfs(v,u);
printf("%d ",u);
cnt[u]--;
}
while (cnt[u]--) printf("%d ",u);
}
void solve()
{
m=0;
for (int i=1;i<=n;++i) cnt[i]=0,e[i].clear();
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d",&a[i]),cnt[a[i]]++;
for (int i=1;i<=n;++i)
if (cnt[i]) q[++m]={i,cnt[i]};
LL sum=0;
for (int i=1;i<=n;++i) sum+=min(m-1,cnt[i]);
if (sum<2*m-2)
{
sort(a+1,a+1+n);
for (int i=1;i<=n;++i) printf("%d ",a[i]);
printf("\n");
return;
}
sort(q+1,q+1+m);
int t=1;
while (q[t].num==1) t++;
for (int i=1;i<m;++i)
{
if (i==t) t++;
e[q[i].col].push_back(q[t].col);
e[q[t].col].push_back(q[i].col);
q[t].num--;
if (q[t].num==1&&t!=m) t++;
}
dfs(q[m].col,0);
printf("\n");
}
int main()
{
int T;
for (scanf("%d",&T);T--;) solve();
return 0;
}