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;
}
posted @ 2026-06-11 21:31  kzssCCC  阅读(7)  评论(0)    收藏  举报