暑假集训Day8 P3472 [POI2008]MAF-Mafia(思维题)

题目大意

你猜呀

输入格式

你猜呀

输出格式

你猜呀

数据范围与提示

你接着猜呀

算法分析

  • 设最少存活人数为MIN 最多存货人数为MAX
    来看张图:

  • 首先我们先来统计一下每个点的入度,如果一个点的入度为0,则自始至终这个人都不可能被杀(没人想杀小姐姐),就像图里的1和4,所以这个人想杀的人一定会死(震惊) 也就是图中的2

  • 每个人都会有想杀的人(咱也不知道为啥会想杀自己),一个人如果入度不为0 ,他想杀的人就不一定会死(为了保护你想杀的小姐姐 先把你干掉)
    这样的点的目标就像3,可能会死掉(如果拯救小姐姐的人来晚了,小姐姐就被大魔王煮着吃了)
    我们可以发现:

  • 如果一个点入度为0(绿框) 那么这个点一定会活下来 :MIN++ MAX++

  • 最小存活:让必定会死的人2先把自己要杀的人3杀掉(undie标记3)(在勇士救出小姐姐之前吃了她)则MIN就不能加1了

  • 最大存活:先把必定会死的人杀死防止他去杀他要杀的人(勇士在魔王吃掉小姐姐之前干掉大魔王),那么最大存活就会加1了(就像例图中3被解救了)
    But 对小姐姐图谋不轨的人远不止一个呢? 因此我们不能直接判定小姐姐是可以存活的,但是我们可以将小姐姐的入度--
    如果入度成功变成了0。 恭喜你,小姐姐得救了(虽然不能和你幸福地生活在一起),我们就可以将这个点入队了(队列维护入度为0的点,表示安全的点)

  • 但是如果这个点的入度并没有变成0 则这个点一定也会构成一个环或者链 ,关于链显然隔一个人打一个人可以存活最多的人(n/2),存活最少的人就是只活一个(很简单,自己推)

  • 如果环上有undie标记的人,可以让这个人最后死,然后在这个环死的只剩他的时候,BOOM 干掉他

  • 当然还有一种情况就是直接的无任何undie标记的环 , 同上
    1.最小存活: +1
    2.最大存活: +n/2

  • 具体的细节去看注释吧

代码展示

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int n,Max,Min,q[maxn],aim[maxn],rd[maxn];
bool die[maxn],undie[maxn];

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d",&aim[i]);
        rd[aim[i]]++;
    }
    for(int i=1;i<=n;++i)
        if(rd[i]==0){//入度为0的点进队 
            Min++;//最小存活++
            q[++Max]=i;//最大存活++
        }
    int head=1;//模拟指针
    while(head<=Max){
        int cur=q[head];head++;//队首出队
        if(die[aim[cur]]) continue;//如果队首要杀的人已经被别人杀了
        die[aim[cur]]=1;//标记队首要杀的人
        int live=aim[aim[cur]];//队首目标的目标可死可不死
        rd[live]--;//入度--
        undie[live]=1;//可以不死了
        if(rd[live]==0)//如果入度变为0了
            q[++Max]=live;//最大存活数++,入队
    }
//下面是处理环的
    for(int i=1;i<=n;++i)
    	if(rd[i] && !die[i]){//如果当前位置入度不为0 并且还没死
            int len=0,flag=0;//len为环的长度 flag判定当前人是否可能会死
            for(int j=i;!die[j];j=aim[j]){//遍历环
                len++;//环长度++
                flag|=undie[j];//当前人是否可能会死
                die[j]=1;//标记为已死 防止下次i到这个位置再计算
            }
            if(!flag && len>1) Min++;//如果当前点不可能会死 而且环长度>1(不是自环) ,最小存活数++
            Max+=len/2;//最大存活数加上环长度的一半
    } 
    printf("%d %d",n-Max,n-Min);//要输出最小死亡数和最多死亡数
    return 0;
}

感谢观看
点个关注>:<

如初见 与初见
原文地址:https://www.cnblogs.com/HISKrrr/p/13215979.html