传送门:点击打开链接
题意:n个点,m条边的无向图,点不一定都连通,有q个查询,每个查询有u和v。现在要把m条边从无向边变成有向边,并要求每个查询的u能通过后来的有向边到达v。问是否存在这样的构造。所有数都<=2e5
思路:这道题写起来太爽了!
首先我们很容易想到通过无向图强连通分量缩点,缩点完后就只剩下了一棵树(或者森林)。
我们接下来考虑不合法的情况,绝对有一节点会满足这个要求:对于节点rt,u是rt子树里的一个节点,u有查询必须要连接到v,而v在rt的子树外。这说明rt所对应的上一条边的边方向已经确定了,但是如果此时在rt的子树外有一个点必须要连一条边到rt的子树里,那么就与前面矛盾了,此时就无答案。
所以我们先通过DFS序给树编号,这样对于一个节点,它的子树中的节点新编号都会在一个连续的区间内。
之后我们只需要维护最大值和最小值,看是否在子树对应的区间外,我们就能知道是否有不满足题意的情况了(噫,语文不是很好,直接看代码我维护的是什么内容把。。
因为可能是森林,所以DFS序编号和后来查询是否满足答案的时候也都要注意是否把所有的通块都遍历完了。
#include <map> #include <set> #include <cmath> #include <ctime> #include <stack> #include <queue> #include <cstdio> #include <cctype> #include <string> #include <vector> #include <cstring> #include <iomanip> #include <iostream> #include <algorithm> #include <functional> #define fuck(x) cout<<"["<<x<<"]" #define FIN freopen("input.txt","r",stdin) #define FOUT freopen("output.txt","w+",stdout) using namespace std; typedef long long LL; typedef pair<int, int> PII; const int MX = 2e5 + 5; int Head[MX], erear; struct Edge { int u, v, nxt; } E[MX << 1]; void edge_init() { erear = 0; memset(Head, -1, sizeof(Head)); } void edge_add(int u, int v) { E[erear].u = u; E[erear].v = v; E[erear].nxt = Head[u]; Head[u] = erear++; } int n, m, q; int DFN[MX], Low[MX], dsz, tim; int Stack[MX], inStack[MX], Belong[MX], bsz, ssz; int MIN[MX][2], MAX[MX][2], L[MX], R[MX]; void DFS_1(int u, int f) { L[u] = ++dsz; MIN[u][0] = MIN[u][1] = dsz; MAX[u][0] = MAX[u][1] = dsz; for(int i = Head[u]; ~i; i = E[i].nxt) { int v = E[i].v; if(v == f) continue; DFS_1(v, u); } R[u] = dsz; } bool DFS_2(int u, int f) { DFN[u] = 1; for(int i = Head[u]; ~i; i = E[i].nxt) { int v = E[i].v; if(v == f) continue; if(!DFS_2(v, u)) return false; MIN[u][0] = min(MIN[u][0], MIN[v][0]); MAX[u][0] = max(MAX[u][0], MAX[v][0]); MIN[u][1] = min(MIN[u][1], MIN[v][1]); MAX[u][1] = max(MAX[u][1], MAX[v][1]); } bool a = (MIN[u][0] < L[u] || MAX[u][0] > R[u]); bool b = (MIN[u][1] < L[u] || MAX[u][1] > R[u]); if(a && b) return false; return true; } void trajan(int u, int e) { inStack[u] = 1; Stack[++ssz] = u; DFN[u] = Low[u] = ++dsz; for(int i = Head[u]; ~i; i = E[i].nxt) { int v = E[i].v; if((i ^ 1) == e) continue; if(!DFN[v]) { trajan(v, i); Low[u] = min(Low[u], Low[v]); } else if(inStack[v]) { Low[u] = min(Low[u], Low[v]); } } if(DFN[u] == Low[u]) { bsz++; int v; do { v = Stack[ssz--]; inStack[v] = 0; Belong[v] = bsz; } while(ssz && v != u); } } void tarjan_solve(int n) { dsz = bsz = ssz = 0; memset(DFN, 0, sizeof(DFN)); for(int i = 1; i <= n; i++) { if(!DFN[i]) trajan(i, -1); } edge_init(); for(int i = 0; i < 2 * m; i += 2) { int u = E[i].u, v = E[i].v; u = Belong[u]; v = Belong[v]; if(u == v) continue; edge_add(u, v); edge_add(v, u); } } int main() { edge_init(); //FIN; scanf("%d%d%d", &n, &m, &q); for(int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); edge_add(u, v); edge_add(v, u); } tarjan_solve(n); dsz = 0; for(int i = 1; i <= bsz; i++) { DFN[i] = 0; if(!L[i]) DFS_1(i, -1); } bool ans = true; for(int i = 1; i <= q; i++) { int u, v; scanf("%d%d", &u, &v); u = Belong[u]; v = Belong[v]; MIN[u][0] = min(MIN[u][0], L[v]); MAX[u][0] = max(MAX[u][0], L[v]); MIN[v][1] = min(MIN[v][1], L[u]); MAX[v][1] = max(MAX[v][1], L[u]); } for(int i = 1; i <= bsz; i++) { if(!DFN[i] && !DFS_2(i, -1)) ans = false; } printf("%s\n", ans ? "Yes" : "No"); return 0; }