CF2208D 思路分享(构造)
https://codeforces.com/problemset/problem/2208/D2
题意概述
给定一个 \(n\times n\) 的矩阵 \(s\),\(s[i][j]=1\) 表示存在 \(i\) 到 \(j\) 的路径,反之不存在.
需要构造出一棵树,给树的每条边定向,使得矩阵 \(s\) 表达的可达关系与树定向后形成的图一致.
\(2\le n \le 8000\).
思路
首先把 \(s\) 当成邻接矩阵建图,判断建出来的图是不是 \(DAG\),如果不是直接输出 \(NO\).
记 \(R_u\) 为所有从 \(u\) 出发可达的点,分析可知,在一条链 \(v_1,v_2,\cdots,v_k\) 上,满足
\[R_{v_1} \supset R_{v_2} \supset \cdots \supset R_{v_k}
\]
枚举每个顶点 \(u\),连出所有与 \(u\) 的边.
根据上面结论,需要优先考虑 \(R\) 最大的 \(v\),连 \(u\)-\(v\) 的边,然后把所有 \(R_v\) 中元素从 \(R_u\) 中删除.
建图时,当边的数量超过 \(n-1\) 时立刻退出,避免退化到 \(\mathcal{O}(n^3)\).
建完图后,需要判断忽略边的方向后图是不是一棵树,同时还需要判断可达关系是否与 \(s\) 一致.
时间复杂度 \(\mathcal{O}(n^2)\).
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class dsu{
public:
int n,cnt_cc;
vector<int> p,sz;
dsu(int _n){
n = _n;
cnt_cc = n;
p = vector<int>(n+1);
for (int i=1;i<=n;i++){
p[i] = i;
}
sz = vector<int>(n+1,1);
}
int find(int x){
int root = x;
while (p[root]!=root) root = p[root];
while (x!=root){
int next = p[x];
p[x] = root;
x = next;
}
return root;
}
void unite(int a,int b){
a = find(a);
b = find(b);
if (a==b) return;
if (sz[a]>=sz[b]){
sz[a] += sz[b];
p[b] = a;
}
else{
sz[b] += sz[a];
p[a] = b;
}
cnt_cc--;
}
};
void solve(){
int n;
cin >> n;
vector<string> s(n+1);
for (int i=1;i<=n;i++){
cin >> s[i];
s[i] = ' '+s[i];
}
auto check = [&](){
vector<int> ing(n+1);
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if (i==j || s[i][j]=='0') continue;
ing[j]++;
}
}
queue<int> q;
for (int i=1;i<=n;i++){
if (ing[i]==0){
q.push(i);
}
}
while (!q.empty()){
int u = q.front();
q.pop();
for (int v=1;v<=n;v++){
if (v!=u && s[u][v]=='1'){
if (--ing[v]==0){
q.push(v);
}
}
}
}
if (*max_element(ing.begin()+1,ing.end())!=0){
return false;
}
else{
return true;
}
};
if (!check()){
cout << "NO" << '\n';
return;
}
vector<pair<int,int>> res;
vector<int> cnt(n+1);
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if (i!=j && s[i][j]=='1'){
cnt[i]++;
}
}
}
vector<int> ord(n+1);
iota(ord.begin(),ord.end(),0);
sort(ord.begin()+1,ord.end(),[&](int i,int j){
return cnt[i]>cnt[j];
});
vector<vector<int>> adj(n+1);
for (int tt=1;tt<=n;tt++){
int i = ord[tt];
vector<bool> vis(n+1,false);
for (int tp=1;tp<=n;tp++){
int j = ord[tp];
if (j==i || vis[j] || s[i][j]=='0') continue;
res.emplace_back(i,j);
if (res.size()>n-1){
cout << "NO" << '\n';
return;
}
adj[i].push_back(j);
for (int k=1;k<=n;k++){
if (s[j][k]=='1'){
vis[k] = true;
}
}
}
}
if (res.size()!=n-1){
cout << "NO" << '\n';
return;
}
dsu ds(n);
for (auto& [u,v]:res){
ds.unite(u,v);
}
if (ds.cnt_cc!=1){
cout << "NO" << '\n';
return;
}
vector<bool> vis(n+1,false);
vector<bitset<8005>> bs(n+1);
function<void(int)> dfs = [&](int u){
bs[u][u] = 1;
vis[u] = true;
for (auto& v:adj[u]){
dfs(v);
bs[u] |= bs[v];
}
};
for (int i=1;i<=n;i++){
if (!vis[i]){
dfs(i);
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if (bs[i][j]!=s[i][j]-'0'){
cout << "NO" << '\n';
return;
}
}
}
cout << "YES" << '\n';
for (auto& [u,v]:res){
cout << u << ' ' << v << '\n';
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号