洛谷 P11337 「COI 2019」IZLET 题解
如果每条边连接的两个点颜色都不相同,那么可以使用如下策略确定每个点的颜色:
令 \(c_{i,j}\) 为 \(i\) 到 \(j\) 的路径上的颜色数。对于每个点 \(u\),以其为根进行一次 dfs,往下找直到找到一个和它颜色相同的或者一个叶子就回溯,如果遇到颜色相同的就将它们在并查集上合并。考虑如何在上面的过程中判断一个点 \(v\) 是否和它颜色相同:假设 \(u\) 到 \(v\) 的路径上除了 \(u\) 外离 \(u\) 最近的点为 \(s\)(即 \(u\) 的某个儿子),只需要判断是否有 \(c_{u,v}=c_{s,v}\)。
如果存在边连接的两个点颜色相同怎么办?考虑本质上可以把同样颜色的点构成的连通块“缩”成一个点(两个点 \(u,v\) 在同一个同色连通块里的充要条件为 \(c_{u,v}=1\),可以用并查集维护)。显然最终构造出的树中这个同色连通块的形态是不重要的,所以可以用上面的每个连通块的“代表元”建树(可以证明在图上随便跑出一棵生成树都是对的),跑上一段所述的流程,之后把连通块中的其他点全部连到这个点上即可。
放代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
namespace IAOI_lib{
template<typename T> class dsu{
private:
vector<T> a;
vector<int> s;
public:
dsu(int n=2e5){
a.resize(n),s.resize(n,1);
iota(a.begin(),a.end(),0);
}
T leader(T x){
return a[x]==x?x:a[x]=leader(a[x]);
}
inline int size(T x){
return s[leader(x)];
}
inline void merge(T x,T y){
x=leader(x),y=leader(y);
if(x==y)return;
if(s[x]>s[y])swap(x,y);
s[y]+=s[x],a[x]=y;
}
inline bool same(T x,T y){
return leader(x)==leader(y);
}
}; // 并查集模板
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t,n; cin>>t>>n;
vector a(n,vector<int>(n));
for(auto &i:a)for(auto &j:i)cin>>j;
IAOI_lib::dsu<int> d(n),d1(n),d2(n);
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
if(a[i][j]==1)d.merge(i,j); // 在一个同色连通块里
vector<vector<int> > g(n);
vector<pii> e;
for(int i=0;i<n;i++)
if(i==d.leader(i))
for(int j=i+1;j<n;j++)
if(j==d.leader(j)&&a[i][j]==2&&!d1.same(i,j)){
g[i].emplace_back(j);
g[j].emplace_back(i);
e.emplace_back(i,j),d1.merge(i,j);
} // 用代表元建树
for(int i=0;i<n;i++)
if(i!=d.leader(i))
e.emplace_back(i,d.leader(i));
// 把同色连通块内其他结点连向代表元
function<void(int,int,int,int)> dfs=[&](int u,int f,int r,int s){
if(~s&&a[r][u]==a[s][u])
d2.merge(r,u); // 颜色相同
else for(int i:g[u])
if(i!=f)dfs(i,u,r,s<0?i:s);
}; // dfs 染色过程
for(int i=0;i<n;i++)
if(i==d.leader(i))dfs(i,i,i,-1);
for(int i=0;i<n;i++)
cout<<d2.leader(d.leader(i))+1<<' ';
cout<<endl;
for(auto [u,v]:e)
cout<<u+1<<' '<<v+1<<'\n';
return 0;
}