安吉D12-T3
- 1安吉D19 T1
- 2安吉D19 T3
- 3安吉D20 模考12总结
- 4安吉D17 T3
- 5安吉D17 T4
- 6安吉D19 模考11总结
- 7安吉D15 A
- 8安吉D16 T4
- 9安吉D17 T1
- 10安吉D17 模考10总结
- 11安吉D16 T3
- 12安吉D16 模考9总结
- 13安吉D13 模考8总结
- 14安吉D13-T2
- 15安吉D13-T3
- 16安吉D12-T3本文
- 17安吉D11-B
- 18安吉D11-C
- 19安吉D11-D
- 20安吉D12 模考7总结
- 21安吉D12-T2
- 22安吉D11-A
- 23安吉D10 模考6总结
- 24安吉D10-T3
- 25安吉D10-T4
- 26安吉D8-E
- 27安吉D9-T4
- 28安吉D8-A
- 29安吉D8-B
- 30安吉D8-C
- 31安吉D8-D
- 32安吉D9 模考5总结
- 33安吉D9-T2
- 34安吉D9-T3
- 35安吉D6-T4
- 36安吉D4-G
- 37安吉D5-T2
- 38安吉D6 模考4总结
- 39安吉D6-T1
- 40安吉D6-T3
- 41安吉-开营测试 T4
- 42安吉D5 模考3总结
- 43安吉D5-T3
- 44安吉D4-A
- 45安吉D4-B
- 46安吉D4-C
- 47安吉D4:容斥原理
- 48安吉Day4-D
- 49安吉D3 模考2总结
- 50安吉D3-T1
- 51安吉D3-T2
- 52安吉D3-T4
- 53安吉D2 模考1总结
- 54安吉D2-T2
- 55安吉D1-G
- 56安吉D1-L
- 57安吉D2-T4
原题呈现
遗忘与新生
题目背景
小信经营着一座“天体希尔伯特旅馆”.旅馆的房间编号遍布全部整数,而房间会按照编号对 取模后的非负余数分配到不同楼层.
题目描述
旅馆共有 个楼层.若两个房间编号除以 的非负余数相同,则它们位于同一楼层.
现在有 条搬迁指令.每条指令形如 ,表示:所有与房间 位于同一楼层的住客,都可以搬进编号为自己的房间号加上 的新房间.
对于每次查询给出的初始房间号 ,假设这 条指令可以按任意顺序、任意次数反复使用.你需要判断:从房间 出发,住客所有可能到达的房间号所形成的集合,是否是一个无限集合.
输入格式
第一行包含三个整数 .
接下来 行,每行两个整数 ,表示一条指令.
接下来 行,每行一个整数 ,表示一个查询.
输出格式
对于每次查询,如果可能到达的房间号集合是无限集,则输出 Yes;否则输出 No.
样例
样例输入 #1
3 2 31 1-1 3123样例输出 #1
YesYesNo样例输入 #2
3 2 31 1-1 0123样例输出 #2
NoNoNo数据范围
子任务
| Subtask | 分值 | 特殊限制 |
|---|---|---|
| 1 | 10 pts | |
| 2 | 10 pts | 对所有指令都满足 |
| 3 | 20 pts | 对每个余数 ,至多存在一条指令满足 |
| 4 | 20 pts | |
| 5 | 40 pts | 无特殊限制 |
观察题目的“搬迁指令”,每一条允许与模 为特定值的住户移动到另一层.我们知道,一名住户具体在哪一个房间并不重要,重要的是在哪一个楼层.因此,可以将每一个楼层抽象成一个点,将每一条搬迁指令抽象成一条边,从而组成一张图.
如果题目表述满足有一些方法可以改变一种状态,并且每一种方法都可以使用多次,这就是一张以这种状态的每一个值为端点,每一种方法为边的有向图.若方法是可逆的,则是无向图.
那么什么样的住户所到的端点会是“无限集”呢?想象一下,让一个点在图上走,如果这个点走着走着不能走了,没路了,那么这个点所到的端点就不是“无限集”.显然,只有在环上运动才不会无路可走,即一个用户可以通过一些操作到回到自己原来的楼层.但是,如果通过这些操作最终回到的还是原来那个房间,那么住户到达的还是有限集.比如说以下这个样例:
3 2 11 23 -21住户通过第一条指令从一号房搬到三号房,又通过第二条指令从三号房搬回一号房.这显然是一个环,但是不满足题意(只到达了一号和三号房).
因此,对于一个住户,若该住户最终可以在一个环上运动,且移动的距离和不为 0,则这名住户所到的房间端点是一个无限集.抽象到图上,若以每一个搬迁指令的移动数量为这个指令对应边的权值,则符合条件的用户最终会走到一个权值和非 0 的环上.
那么首先,就需要把环求出来,就是求出图中的强连通分量.这部分可以用 tarjan 实现.
之后,对于每一个强连通分量,我们要判断其中有没有权值和非 0 的环.这部分可以使用 bfs 实现.我们知道,对于一个权值和为 0 的环,其满足若随机一个点为起始点,每一个点到起始点的距离唯一.因此,在环上进行遍历,并记录该点到起始点的距离.若第二次遍历到某点时,此时的权值与第一次不同(发现一个起始点到达该点距离不唯一的点),则这个强连通分量中必然存在权值非 0 的环.
如上图,若选定 A 为起始点,我们会发现,从 A 点到 B 点有多种距离.其一为 2,,其二为 4,.说明这个连通分量中含有权值和非 0 环.
我们对于每一个强连通分量,都跑一遍 bfs,如果该强连通分量中含有权值和非 0 环,则将这个分量打上标记.之后将每个强连通分量缩点(详见强连通分量 - 缩点),从而形成一个 DAG.
最后,通过逆向的拓扑序处理每一个节点 .逆向拓扑序可以保证在处理每一个节点时,其可以到达的节点已经处理过了.在处理时,遍历所有能够一步到达 的节点 ,若 被标记了,那么可以把标记传到 .根据缩点的相关知识,我们知道,tarjan 缩点后的点排列顺序天然是逆拓扑序.于是只需要根据 tarjan 缩点后点排列的顺序将标记进行扩散即可.
最后的最后,根据每一次询问,都检查:该点是否被打上了标记,如果是,输出 Yes,否则,输出 No.
标程
#include <bits/stdc++.h>using namespace std;const int N = 5e5 + 100;typedef long long ll;#define int llconst ll INF = 0x3f3f3f3f3f3f3f3f;int n, m, q, dfn[N], low[N], cnt, bel[N], scc_cnt = 0;bool in_stack[N], mark[N];stack<int> s;struct Node { int v, w;};vector<Node> e[N];vector<int> g[N];ll h[N];int mod(int x) { return (x % n + n) % n; }
void tarjan(int u) { if (dfn[u]) return; dfn[u] = ++cnt; low[u] = dfn[u]; s.push(u); in_stack[u] = true; for (int i = 0; i < e[u].size(); i++) { int v = e[u][i].v; if (dfn[v] == 0) { tarjan(v); low[u] = min(low[u], low[v]); } else if (in_stack[v]) { low[u] = min(low[u], dfn[v]); } } if (low[u] == dfn[u]) { scc_cnt++; set<int> v; int top; do { top = s.top(); s.pop(); in_stack[top] = false; bel[top] = scc_cnt; v.insert(top); } while (top != u); }}
bool check_scc(int start) { queue<int> qu; qu.push(start); h[start] = 0; while (!qu.empty()) { int u = qu.front(); qu.pop(); for (int i = 0; i < e[u].size(); i++) { int v = e[u][i].v; int w = e[u][i].w; if (bel[v] != bel[u]) { continue; } if (h[v] == INF) { h[v] = h[u] + w; qu.push(v); } else if (h[v] != h[u] + w) { return true; } } } return false;}
signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m >> q; for (int i = 1; i <= m; i++) { int a, b; cin >> a >> b; e[mod(a)].push_back({mod(a + b), b}); } for (int i = 0; i < n; i++) { tarjan(i); } memset(h, 0x3f, sizeof(h)); for (int u = 0; u < n; u++) if (h[u] == INF && !mark[bel[u]]) mark[bel[u]] = check_scc(u); for (int u = 0; u < n; u++) { for (auto v : e[u]) { int a = bel[u], b = bel[v.v]; if (a != b) { g[a].push_back(b); } } } for (int u = 1; u <= scc_cnt; u++) { for (auto v : g[u]) { mark[u] |= mark[v]; } } for (int i = 1; i <= q; i++) { int x; cin >> x; x = mod(x); if (mark[bel[x]]) { cout << "Yes" << endl; } else { cout << "No" << endl; } } return 0;}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


