AT_abc435_d 题解
前言
赛时脑子大爆炸,没做出来。。。。
其实简单的很。。。
题面
题目描述
先给你一张有向图,有\(n\)个顶点和\(m\)条边,顶点编号为\(1\)到\(n\),第\(i\)条边从\(x_i\)指向\(y_i\),初始时所有点都是白色的。
然后\(q\)次操作。
每次有两种可能的操作。
-
1 v,表示把顶点\(v\)涂黑 -
2 v,表示查询\(v\)点能不能通过若干条有向边走到一个黑色点,能则输出Yes,不能则输出No
数据范围
- \(n,m,q \leq 10^5\)
- 所有操作仅可能是上述两种操作的一种。
- \(x_i,y_i \leq n\)
- 没有重边,即不存在\(i,j\) (\(i < j \leq n\)) 满足\((x_i,y_i) = (x_j,y_j)\)
- 没有自环,即不存在\(k\)(\(k \leq n\)) 满足\(x_k = y_k\)
思考历程
考场上:
首先可以建个反图
what's 反图?
对于一个\(n\)顶点,\(m\)条边的有向图,其反图也有\(n\)个顶点,\(m\)条边。每条边连接的两个点也一样,但是每条边的方向都颠倒
然后可以把所有的点在反图上\(dfs\)跑一遍,记录每个点有哪些点可以通过原图走到。
然后在操作\(1\)的时候把那个点的所有记录的点全部标记。
操作\(2\)时看看这个点有没有标记过。
其中如果操作1时这个点已经被操作1过了,就可以直接跳过。
那么就可以得到代码:
#include <bits/stdc++.h>
using namespace std;
vector<int> graph[300001];
bool vis[300001];
bool g[300001];
vector<int> v[300001];
void dfs(int x, int p)
{
v[x].push_back(p);
for (int i : graph[p])
{
if (!vis[i])
{
vis[i] = true;
dfs(x, i);
}
}
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
graph[y].push_back(x); // 返图
}
for (int i = 1; i <= n; i++) // 每个点跑一遍
{
memset(vis, 0, sizeof(vis)); // 记得清空
dfs(i, i);
}
memset(vis, 0, sizeof(vis)); // 重复利用
int q;
cin >> q;
while (q--)
{
int op;
cin >> op;
if (op == 1)
{
int x;
cin >> x;
if (vis[x]) continue; // 有过操作1,就跳过
vis[x] = true; // 现在有过了
for (int i : v[x])
{
g[i] = true; // 标记这些在原图上可以到达这个点的所有点
}
}
else
{
int x;
cin >> x;
cout << (g[x] ? "Yes" : "No") << endl; // 看看有没有标记过
}
}
return 0;
}
结果是AC \(20\),TLE \(10\),然后考场上就没突破了。
考后
继续进行思考,发现不需要提前把所有的点都在反图上跑一遍,在操作1的时候可以再去做。
然后继续思考,发现操作1时如果点被标记(标记可以到达一个黑色点)过,就不需要去跑了。因为能到达这个点的所有点也都能到达这个点能到达的黑色点。
又可以得到代码:
#include <bits/stdc++.h>
using namespace std;
vector<int> graph[300001];
bool vis[300001];
bool g[300001];
vector<int> v[300001];
void dfs(int x, int p)
{
v[x].push_back(p);
for (int i : graph[p])
{
if (!vis[i])
{
vis[i] = true;
dfs(x, i);
}
}
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
graph[y].push_back(x); // 建反图
}
int q;
cin >> q;
while (q--)
{
int op;
cin >> op;
if (op == 1)
{
int x;
cin >> x;
if (g[x]) continue; // 标记过就不用跑了
memset(vis, 0, sizeof(vis)); // 记得清空
dfs(x, x); // 跑一遍
g[x] = true; // 标记本身点
for (int i : v[x])
{
g[i] = true; // 标记所有在原图上能到的这个点的点。
}
}
else
{
int x;
cin >> x;
cout << (g[x] ? "Yes" : "No") << endl; // 有没有被标记过
}
}
return 0;
}
得到AC \(24\), TLE \(6\)
继续继续,发现跑\(dfs\)的时候如果跑到之前标记过的点就可以回溯了,因为标记过的点可以到达别的黑色点,那么如果继续往下搜,搜到的点也都可以在原图上到达这个标记过的点,也就都可以到达某一个黑色点。
得到代码:
#include <bits/stdc++.h>
using namespace std;
vector<int> graph[300001];
bool vis[300001];
bool g[300001];
vector<int> v[300001];
void dfs(int p) // 因为不需要每个点求了,所以不需要第一个参数了
{
g[p] = true; // 标记一下
for (int i : graph[p])
{
if (!g[i]) // 没标记过再搜索
{
dfs(i);
}
}
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
graph[y].push_back(x); // 反图
}
int q;
cin >> q;
while (q--)
{
int op;
cin >> op;
if (op == 1)
{
int x;
cin >> x;
if (g[x]) continue; // 标记过
dfs(x); // 跑一遍
g[x] = true;
for (int i : v[x])
{
g[i] = true; // 标记能走到这个点的点
}
}
else
{
int x;
cin >> x;
cout << (g[x] ? "Yes" : "No") << endl; // 标记过吗?
}
}
return 0;
}
成功得到AC \(30\)
完结撒花~~~~~

浙公网安备 33010602011771号