信息学奥赛一本通1732:情报传递

原题链接: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");
	}
}