题解 P4819

前言:

看到目前的题解当中没有并查集做法,于是写一篇水水。

题目描述:

给定一张图,一个图中有黑白两种颜色,已知黑色的点有且只有一个,且每个点是黑色的概率相等,然后点 \(u\) 与点 \(v\) 之间的边表示 \(u\) 知道 \(v\) 是什么颜色,我们的目的是找到黑点。

问在达成目的并且看点颜色次数最少的情况下,看的点的颜色不是黑色的概率为多少。

题意分析:

首先,根据给出的题意,不难得出这样的结论:

如果当前在一个环上,则我们可以从环上任意一个点出发,若该点不是黑色,则可以在不看到黑色的情况下知道整个环的点的颜色。

根据此条性质,我们可以对原图进行缩点。

然后,考虑维护一个并查集,对缩点后得到的新图的每个点进行遍历,看看当前点 \(u\) 能访问到那些点 \(v\),然后按照如下规则进行两个 \(u\)\(v\) 点的信息合并:

  1. 若点 \(v\) 没有被合并,则将 \(v\) 合并到 \(u\) 所在的集合中。
  2. 若点 \(u\) 被合并了,则不进行合并。

最后,我们统计集合大小为 \(\boldsymbol{1}\) 的集合的个数 \(\boldsymbol{a}\) 集合大小大于 \(\boldsymbol{1}\) 的集合的个数 \(\boldsymbol{b}\)

此时,需要看的点的个数 \(k\) 如下:

  1. \(a=0\),则 \(k = b\)
  2. 否则,若 \(a>1\),则 \(k = a + b - 1\)
  3. 否则,若 \(n=1\),则 \(k = 0\)

最后,我们的答案就是 \(\dfrac kn\)(请别忘了保留 \(6\) 位小数)。

注意

  1. 本题使用 long double 会炸掉精度,请使用 double 计算答案。
  2. 本题请务必要特判 \(n=1\) 的情况,因为当 \(n=1\) 的时候,那个点必定是黑点,不需要去看。

综上,时间复杂度 \(O(n \lg n)\)

代码实现:

#include <bits/stdc++.h>
#define debug(x) cerr << #x << ": " << x << endl;
#define int long long
using namespace std;
const int N = 3e5 + 9;
#define MAX_SIZE (int)1.1e5
struct node
{
    int to, next;
};
node edge[N];
int n, m, u, v, tot, cnt, sum, top, res, flag, cnt1;
int head[MAX_SIZE];
int dfn[MAX_SIZE];
int low[MAX_SIZE];
int sta[MAX_SIZE];
int col[MAX_SIZE];
int ind[MAX_SIZE];
int siz[MAX_SIZE];
bool vis[MAX_SIZE];

void add(int u, int v)
{
    edge[++tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++cnt;
    vis[u] = 1, sta[++top] = u;
    for (int i = head[u]; i; i = edge[i].next)
    {
        int v = edge[i].to;
        if (!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (vis[v])
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (dfn[u] == low[u])
    {
        col[u] = ++sum;
        vis[u] = 0;
        siz[sum] = 1;
        while (sta[top] != u)
        {
            col[sta[top]] = sum;
            vis[sta[top]] = 0;
            --top;
            siz[sum]++;
        }
        --top;
    }
    return;
}

int head2[MAX_SIZE];
int Next[MAX_SIZE];
int ver[MAX_SIZE];
int tot2 = 0;

void Add(int u, int v)
{
    ver[++tot2] = v;
    Next[tot2] = head2[u];
    head2[u] = tot2;
}

int fa[MAX_SIZE];
int fasiz[MAX_SIZE];

void init(int size)
{
    for (int i = 1; i <= size; i++)
    {
        fa[i] = i;
        fasiz[i] = 1;
    }
}


int getfa(int x)
{
    if (x == fa[x])
        return fa[x];
    return fa[x] = getfa(x);
}

void merge(int u, int v)
{
    int x = getfa(u);
    int y = getfa(v);
    fa[y] = x;
    fasiz[x] += fasiz[y];
}

signed main()
{
    ios::sync_with_stdio(false);
#ifdef LOCAL
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
    double c1 = clock();
#endif
    //============================================
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        cin >> u >> v;
        add(u, v);
    }

    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            tarjan(i);

    for (int u = 1; u <= n; u++)
    {
        for (int i = head[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if (col[u] != col[v])
            {
                Add(col[u], col[v]);
                ++ind[col[v]];
            }
        }
    }

    init(sum);
    for (int u = 1; u <= sum; u++)
    {
        for (int i = head2[u]; i; i = Next[i])
        {
            int v = ver[i];
            if (fa[v] != v)
                continue;
            merge(u, v);
        }
    }
    int lookup = 0;
    int lonely = 0;
    for (int i = 1; i <= sum; i++)
    {
        if (fa[i] == i)
        {
            if (fasiz[i] > 1)
                ++lookup;
            else
                ++lonely;
        }
    }

    if (lonely)
    {
        if (lonely > 1)
            --lonely;
        else if (lookup)
            --lonely;
        else if (n == 1)
            lonely = 0;
    }
    printf("%.6lf\n", (double)(n - lookup - lonely) / (double)(n));
    //============================================
#ifdef LOCAL
    double c2 = clock();
    cerr << "Used Time: " << c2 - c1 << "ms" << endl;
    if (c2 - c1 > 1000)
        cerr << "Warning!! Time Limit Exceeded!!" << endl;
    fclose(stdin);
    fclose(stdout);
#endif
    return 0;
}
posted @ 2023-02-16 16:50  Larry76  阅读(23)  评论(0)    收藏  举报