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\)

完结撒花~~~~~

posted @ 2025-12-07 09:15  MichaelZeng  阅读(22)  评论(0)    收藏  举报