原题链接:https://ybt.ssoier.cn/problem_show.php?pid=1732
【题目描述】
有一个情报网共有n个人,通过有向的电话线联络。为保证通信安全,需要满足一些要求,这些要求分为两类:
①从第a个人通过一条或多条电话线可以联系到第b个人;
②从第a个人通过一条或多条电话线不能联系到第b个人。
现在作为总工程师的你需要构造一个合法的情报网,使得这个情报网满足给定要求,或者告诉情报机构这样的情报网是不存在的。
【输入】
第一行一个整数n表示人数。
第二行一个整数m表示第一类要求的个数,接下来m行每行两个整数a,b表示要求。
第m+3行一个整数t表示第二类要求的个数,接下来t行每行两个整数a,b表示要求。
【输出】
若不存在这样的情报网,输出一行"NO"(不含引号)。
否则在第一行输出"YES"(不含引号),在第二行输出情报网中电话线的数量P,接下来P行每行两个整数u,v,描述一条u→v的电话线。由于资源有限,要求\(P≤n+m+t\)。
【输入样例】
3
2
1 2
2 3
1
1 3
【输出样例】
NO
【提示】
【输入样例2】
3
2
1 2
2 3
1
3 1
【输出样例2】
YES
2
1 2
2 3
【数据规模】
对于100%的数据,\(1≤n,m,t≤10^5,1≤a,b≤n,a≠b\)。
贪心构造+强联通分量+DAG上dp+bitset+分块的究极神仙题
首先是一个贪心构造,我们可以注意到,对于要求1可以直接连a->b,因为这是最简的,如果这个都满足不了要求2,那么无论你怎么变都无法满足。(贪心嘛,能理解就行)
然后问题就是怎么快速判断a是否能到b了,因为强连通分量缩点之后是一个DAG,会有很多性质,于是便可以先tarjan缩点(其实是分类里标了)
然后就可以用DAG上dp了。具体地,我们先拓扑排序,然后倒着dp,为节约空间,用一个bitset表示强联通分量u是否能到强连通分量v,然后直接判断就行了。
但是我们会发现空间爆了,这时我们注意到他离线的性质,于是便可以用时间换空间,直接分块,每B个点为一块,分批次dp,这样的话空间只需要开N*B就行了,这里实现有点麻烦
code:
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
#include<bitset>
#include<stack>
#include<queue>
using namespace std;
const int N = 100001;
const int B = 512;
int read() {
int k = 0, f = 1;char c = getchar();
for (;c < '0' || c>'9';c = getchar())if (c == '-')f = -1;
for (;c >= '0' && c <= '9';c = getchar())k = (k << 1) + (k << 3) + (c ^ 48);
return k * f;
}
int n, m, T;
int head[N], from[N], to[N], nex[N], cnte = 0;
int dfn[N], low[N], belong[N], scc = 1, idx = 1;
stack<int>st;
vector<int>G[N], G2[N];
int in_du[N], order[N];
int query1[N], query2[N];
bitset<B>can_reach[N];
void add(int u, int v) {
to[cnte] = v;
from[cnte] = u;
nex[cnte] = head[u];
head[u] = cnte++;
}
void tarjan(int u) {
st.push(u);
dfn[u] = low[u] = idx++;
for (int e = head[u];e >= 0;e = nex[e]) {
int v = to[e];
if (dfn[v]==0) {
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (belong[v]==0) {
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
int v;
do {
v = st.top();
st.pop();
belong[v] = scc;
} while (u != v);
scc++;
}
}
void topsort() {
queue<int>q;
for (int i = 1;i < scc;i++) {
in_du[i] = G2[i].size();
if (in_du[i] == 0)q.push(i);
}
int temp = 1;
while (!q.empty()) {
int u = q.front();
order[temp++] = u;
q.pop();
for (int v : G[u]) {
in_du[v]--;
if (in_du[v] == 0)q.push(v);
}
}
}
bool DP(int start,int end) {
for (int i = 1;i < scc;i++)can_reach[i] = 0;
for (int i = start;i <= end;i++) {
can_reach[query2[i]][i - start] = 1;
}
for (int i = scc - 1;i >= 1;i--) {
int u = order[i];
for (int v : G[u]) {
can_reach[u] |= can_reach[v];
}
}
for (int i = start;i <= end;i++) {
if (query1[i] == query2[i])return false;
if (can_reach[query1[i]][i - start])return false;
}
return true;
}
int main() {
n = read(), m = read();
memset(head, -1, sizeof(head));
for (int i = 1;i <= m;i++) {
int u = read(), v = read();
add(u, v);
}
for (int i = 1;i <= n;i++) {
if (!dfn[i])tarjan(i);
}
for (int i = 0;i < m;i++) {
if (belong[from[i]] != belong[to[i]]) {
G[belong[from[i]]].push_back(belong[to[i]]);
G2[belong[to[i]]].push_back(belong[from[i]]);
}
}
topsort();
T = read();
bool ok = true;
for(int i = 1;i<=T;i++){
int u = read(), v = read();
query1[i] = belong[u];
query2[i] = belong[v];
}
for (int i = 0;i <= T;i+=B) {
if (!DP(i + 1, min(T, i + B))) {
ok = false;
break;
}
}
if (ok) {
printf("YES\n%d\n", m);
for (int i = 0;i < m;i++) {
printf("%d %d\n", from[i], to[i]);
}
}
else {
printf("NO");
}
}