第四题 杀人游戏
杀人游戏是一个在信息奥赛选手中流行的游戏,这个游戏不在结果,重在参与,就像信息奥赛一样。这个游戏的一方称为杀手,剩下的一方称为平民。杀手知道每个人的身份,但是平民不知道。在游戏过程中,平民的任务是找出谁是杀手。
游戏玩了若干轮,现在还剩下n个人,每个人都指认了一个杀手。,当然,平民基本是乱猜的,而杀手则全部指认的是平民。在不知道谁是杀手的情况下,最多可能有多少杀手。
输入格式:
第一行包含一个整数N(2<=N<=500000),表示还有n个人。这n个人标号为1到n。
接下来有n行,每行一个数,其中的第k行表示被第k个人指认为杀手的人。
没有谁会指认自己为杀手。
输出格式:
一个整数,表示最多可能的杀手的数量。
40%的数据n<15.
80%的数据n<=2000
输入样例1:
3
2
1
1
输出样例1:
2
输入样例2:
3
2
3
1
输出样例2:
1
输入样例3:
7
3
3
4
5
6
4
4
输出样例3:
4
第一个样例解释:杀手可能为2和3
第二个样例解释:杀手可能是任何1个人,但不可能多于1个,否则杀手就会指认自己人了。
一般的题。过了40%。
有两个思路,一个是贪心。
贪心就是建一个有向图,入度为0的点肯定是杀手,然后这些点以及它们的邻接点全部删掉然后更新入度。如此循环。
具体看代码:
#include<cstdio>
using namespace std;
int to[500010],deg[500010],n,ans;
bool vis[500010];
void dfs(int u,bool flag)
{
if(vis[u])return;
vis[u]=1;
ans+=flag;
deg[to[u]]--;
if(deg[to[u]]==0||flag)
dfs(to[u],!flag);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&to[i]);
deg[to[i]]++;
}
for(int i=1;i<=n;++i)
if(deg[i]==0)dfs(i,1);
for(int i=1;i<=n;++i)
dfs(i,0);
printf("%d\n",ans);
}
事实上我更喜欢第二种思路,就是基环树DP。
这个是一个有n个点n条边的图,可能会有重边或者图不连通的情况。因此这是一个基环树森林。
然后因为杀手不能互相指认,因此这是一个最大独立集问题。
于是就可以跑DP了。
DP方程:f(i,0)=sigma(max(f(soni,1),f(soni,0));
f(i,1)=sigma(f(soni,0))+1。
表示如果没有选i这个点,那么i的儿子可选可不选。
如果选了i这个点,i的儿子不能选。
这个不是重点,重点是怎么做基环树上的DP。
考虑到假设选了环上一点U,那么U的一个邻接点V就不能选了,环从这里断开变成一棵树。分别以U为根和以V为根跑一遍树DP,比较最优解即可。
还需要一个dfs来寻找这个环。
这个图是基环树森林,因此需要遍历每个点,没有被访问过的点都要跑dfs和dp
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500010
using namespace std;
struct node{
int v,next;
}edge[2*maxn];
bool vis[maxn];
int head[maxn],cnt=1,n,U,V,E,f[maxn][2],ans;//cnt从1开始,因为后面有^1的操作
void addedge(int u,int v)
{
edge[++cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u,int fa)//找环
{
vis[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
if(edge[i].v==fa)continue;
if(vis[edge[i].v])
{
U=u;
V=edge[i].v;
E=i;
continue;
}
dfs(edge[i].v,u);
}
}
void dp(int u,int fa)
{
f[u][0]=0;
f[u][1]=1;
for(int i=head[u];i;i=edge[i].next)
{
if(i==E||(i^1)==E||edge[i].v==fa)continue;//这条边不能是剪开的那条边
dp(edge[i].v,u);
f[u][1]+=f[edge[i].v][0];
f[u][0]+=max(f[edge[i].v][0],f[edge[i].v][1]);
}
}
int main()
{
scanf("%d",&n);
for(int u=1;u<=n;++u)
{
int v;
scanf("%d",&v);
addedge(u,v);
addedge(v,u);
}
for(int i=1;i<=n;++i)
if(!vis[i])
{
dfs(i,0);
dp(U,0);
int temp=f[U][0];
dp(V,0);
ans+=max(temp,f[V][0]);
}
printf("%d\n",ans);
}
这篇文章探讨了在杀人游戏中,当剩余玩家进行指认时,最多可能存在的杀手数量。通过构建有向图并应用贪心算法或基环树DP等方法解决此问题。
1053

被折叠的 条评论
为什么被折叠?



