1124: [POI2008]枪战Maf
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 713 Solved: 278
[ Submit][ Status][ Discuss]
Description
有n个人,每个人手里有一把手枪。一开始所有人都选定一个人瞄准(有可能瞄准自己)。然后他们按某个顺序开枪,且任意时刻只有一个人开枪。因此,对于不同的开枪顺序,最后死的人也不同。
Input
输入n人数<1000000 每个人的aim
Output
你要求最后死亡数目的最小和最大可能
Sample Input
8
2 3 2 2 6 7 8 5
Sample Output
3 5
思路:
如果A杀B,那么B向A连一条有向边,这样可以构成一张n个点n条边的有向图,其中每个点入度都一定为1
这种图有一个很好的性质:任意一个连通块,一定都满足边数==点数
这样的话,每个连通块一定只有两种情况:①是个环(包括自环);②环套树
而对于每个连通块,很显然可以单独考虑,最后答案加在一起
先求死亡最大值:
①环:按照杀人顺序逆时针击杀,答案就是环的大小-1(自环除外!答案为1)
②环套树:除了叶子结点(出度为0)以外其它人都可以死,所以答案就时连通块点数 - 叶子个数
再求死亡最小值:
①环:按照杀人顺序顺时针击杀,很好发现答案就是(环的大小+1)/2
②环套树:比较麻烦了
很显然所有叶子结点(出度为0)都一定能存活,同理和它们相连的人(它们的父亲)一定都必死
这些人死后,就可能会导致有新的一批人一定能存活(出度变为0)……依此步骤直到剩下全是环
这样就成情况①了,这个步骤可以用队列模拟
OK
#include<stdio.h>
#include<string.h>
#include<queue>
using namespace std;
queue<int> q;
int sum, fa[1000005], son[1000005], vis[1000005];
void Sech(int x)
{
vis[x] = 1;
if(vis[fa[x]])
return;
sum++;
Sech(fa[x]);
}
int main(void)
{
int n, i, low, hi, x;
//freopen("in.txt", "r", stdin);
scanf("%d", &n);
for(i=1;i<=n;i++)
{
scanf("%d", &fa[i]);
son[fa[i]]++;
}
hi = low = 0;
for(i=1;i<=n;i++)
{
if(son[i]==0)
{
sum = 0;
Sech(i);
hi += sum;
}
}
for(i=1;i<=n;i++)
{
if(vis[i]==0)
{
sum = 0;
Sech(i);
hi += max(sum, 1);
}
}
memset(vis, 0, sizeof(vis));
for(i=1;i<=n;i++)
{
if(son[i]==0)
{
vis[i] = 1;
q.push(i);
}
}
while(q.empty()==0)
{
x = q.front();
q.pop();
if(vis[fa[x]]==0)
{
low++, vis[fa[x]] = 1;
son[fa[fa[x]]]--;
if(son[fa[fa[x]]]==0 && vis[fa[fa[x]]]==0)
{
vis[fa[fa[x]]] = 1;
q.push(fa[fa[x]]);
}
}
}
for(i=1;i<=n;i++)
{
if(vis[i]==0)
{
sum = 0;
Sech(i);
low += (sum+2)/2;
}
}
printf("%d %d\n", low, hi);
return 0;
}
/*
4
2 3 2 2
*/