杀人游戏——深搜

题目描述

  • 一位冷血的杀手潜入 Na-wiat,并假装成平民。警察希望能在 N 个人里面查出谁是杀手。
  • 警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人, 谁是杀手, 谁是平民。假如查证的对象是杀手,杀手将会把警察干掉。
  • 现在警察掌握了每一个人认识谁。
  • 每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。
  • 问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?

输入格式

  • 第一行有两个整数 N,M。
  • 接下来有 M 行,每行两个整数 x,y,表示 x 认识 y(y 不一定认识 x,例如President同志) 。

输出格式

  • 仅包含一行一个实数,保留小数点后面 6 位,表示最大概率。

样例输入

5 4 
1 2 
1 3 
1 4 
1 5 

样例输出

0.800000

数据范围与提示

  • 警察只需要查证 1。假如1是杀手,警察就会被杀。假如 1不是杀手,他会告诉警察 2,3,4,5 谁是杀手。而 1 是杀手的概率是 0.2,所以能知道谁是杀手但没被杀的概率是0.8。
  • 对于 100%的数据有 1≤N ≤ 10 0000,0≤M ≤ 30 0000

Solve

  • 这道题都用的Tarjan,那不麻烦吗,Dfs就搞定。
  • 这道题说是求概率,其实不是概率DP,可以转换一下,其实就是求在最优情况下询问的最少的人数使得警察得知每个人的情况。
  • 我们可以知道,问了一个人,则他所认识的人及他认识的人的认识的人及...的情况都会被警察安全的得到,那建单向边,从入度为0的点出发将可以到达的点染色,最后染了x种颜色,答案就是(n-x)/n.
  • 但是如果有环呢?就得再进行一次遍历。
  • 还有一个要注意的地方,如果处理完了之后还剩下一个点,其实是不用处理的,因为知道了别的人的情况,这个人的情况就一定可以推得了。

上代码

void Add(int x, int y) {//加边
    e[++tot] = (Side) {y, head[x]};
    head[x] = tot;
}
void Dfs(int x) {//进行染色标记
    s--;
    v[x] = 1;
    for (int i = head[x]; i; i = e[i].next)
        if (!v[e[i].t]) Dfs(e[i].t);
}
int main() {
    scanf("%d%d", &n, &m);
    s = n;//s表示未染色的点的个数
    while (m--) {
        int x, y;
        scanf("%d%d", &x, &y);
        Add(x, y);//建立单向边
        ++d[y];//d数组统计入度
    }
    for (int i = 1; i <= n && s; ++i) {//遍历入度为0的点
        if (d[i]) continue;
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;//l-s是本次染色染了几个点
    }
    for (int i = 1; i <= n && s; ++i) {//便利在环中的点
        if (v[i]) continue;
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;//同上
    }
    if (f) --ans;//f是标记图中是否存在大小为1的染色块,如果存在,就有一个人不用询问
    printf("%.6lf", (double)(n - ans) / n);
    return 0;
}
  • 其实这样就可以水过去了(数据有点水)
    看看这个数据
6 6
1 2
2 3
3 1
4 2
6 4
5 3
  • 上面的代码输出是0.666667,实际上答案是0.833333
  • 这是因为我走的时候是按点的编号来的,先从5遍历了整个图,但是从6开始的话,5是可以作为孤立点,可以最后考虑,可以不对答案做贡献。
  • 有两种解决方法
    1. 遍历入度为0的点时正向跑一遍,在反向跑一边,方向不同只会影响到f标记,即图中是否存在可以不用访问就可以通过其他点推出来情况的点,对ans值没有影响,但这种方法不太好证明其正确性。
    2. 在举出的例子中,为什么先遍历5会出错呢?就是因为他的子节点不止有他这唯一的父节点,还可通过别的点染色,这样的点是可以当做孤立点的所以进行3次for循环,第一次遍历入度为0且他的子节点只有他这唯一的父节点,第二次遍历入度为0且他的子节点不只有他这唯一的父节点,第三次遍历在环中的点。

Code1

//第一种方法
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+5;
struct Side {
    int t, next;
}e[N*3];
int head[N], tot;
void Add(int x, int y) {//加边
    e[++tot] = (Side) {y, head[x]};
    head[x] = tot;
}
int n, m, d[N], s, ans;
bool v[N], f;
void Dfs(int x) {//染色
    s--;
    v[x] = 1;
    for (int i = head[x]; i; i = e[i].next)
        if (!v[e[i].t]) Dfs(e[i].t);
}
int main() {
    scanf("%d%d", &n, &m);
    s = n;
    while (m--) {//建立单向边并统计入度
        int x, y;
        scanf("%d%d", &x, &y);
        Add(x, y);
        ++d[y];
    }
    for (int i = 1; i <= n && s; ++i) {//正向遍历入度为0的点
        if (d[i]) continue;
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;//l-s是本次染色染了几个点
    }
    memset(v, 0, sizeof(v));
    ans = 0; 
    s = n;//进行初始化
    for (int i = n; i && s; --i) {//反向遍历入度为0的点
        if (d[i]) continue;
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;//同上
    }
    for (int i = 1; i <= n && s; ++i) {//遍历在环中的点
        if (v[i]) continue;
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;//同上
    }
    if (f) --ans;//如果有孤立点就有一个人可以不用询问
    printf("%.6lf", (double)(n - ans) / n);
    return 0;
}

Code2

//第二种方法
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5+5;
struct Side {
    int t, next;
}e[N*3];
int head[N], tot;
void Add(int x, int y) {
    e[++tot] = (Side) {y, head[x]};
    head[x] = tot;
}
int n, m, d[N], s, ans;
bool v[N], f;
bool judge(int x) {//如果返回0,则这个点可以当作孤立点,要在第二次for循环中遍历
    for (int i = head[x]; i; i = e[i].next)
        if (d[e[i].t] != 1) return 0;
    return 1;
}
void Dfs(int x) {//染色
    s--;
    v[x] = 1;
    for (int i = head[x]; i; i = e[i].next)
        if (!v[e[i].t]) Dfs(e[i].t);
}
int main() {
    scanf("%d%d", &n, &m);
    s = n;
    while (m--) {//建单向边,统计入度
        int x, y;
        scanf("%d%d", &x, &y);
        Add(x, y);
        ++d[y];
    }
    //第一次遍历入度为0且他的子节点只有他这唯一的父节点
    for (int i = 1; i <= n && s; ++i) {
        if (d[i] || !judge(i)) continue;
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;
    }
    //第二次遍历入度为0且他的子节点不只有他这唯一的父节点
    for (int i = 1; i <= n && s; ++i) {
        if (d[i] || v[i]) continue;//这里添了一个v是不要去遍历第一个循环遍历过的点
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;
    }
    //第三次遍历在环中的点
    for (int i = 1; i <= n && s; ++i) {
        if (v[i]) continue;
        int l = s;
        Dfs(i);
        ++ans;
        if (l - s == 1) f = 1;
    }
    if (f) --ans;
    printf("%.6lf", (double)(n - ans) / n);
    return 0;
}
posted @ 2020-07-17 17:28  Shawk  阅读(147)  评论(3编辑  收藏  举报