问题 C: [POI2008]枪战Maf
时间限制: 1 Sec 内存限制: 256 MB
题目描述
有n个人,每个人手里有一把手枪。一开始所有人都选定一个人瞄准(有可能瞄准自己)。然后他们按某个顺序开枪,且任意时刻只有一个人开枪。因此,对于不同的开枪顺序,最后死的人也不同。
输入
输入n人数<1000000 每个人的aim
输出
你要求最后死亡数目的最小和最大可能
样例输入
8
2 3 2 2 6 7 8 5
样例输出
3 5
本次考试最后一个题,被老师评论称难炸了……然而貌似不那么难,但你绝对打不A。
这道题只能怪我代码能力太弱,该想到的都想到了,然而还是华丽丽的爆了0。
首先让我们先明确几条性质来帮助我们做题:
- 对于每个连通图,他至少有一个环或者一个人自杀。缩完点之后它是树或树林。证明:假设我们已经输入了n个人,先忽略一下第n个人要杀谁,如果之前没有人自杀或环的话它一定是一棵树,因为有n个点和n-1条边,那么第n个人指向哪里就很关键了,他指向每一个其他人都会形成一个环,而指向他自己又是自杀,所以,第一条get。
- 每一个没人杀的人都会存活,每一个自杀的人最终都会死。不解释。
- 每个人对于答案的影响不在于他被谁杀掉,而在于他杀掉谁,世界不关心你说了什么,世界只关心你做了什么。
- 一个环如果没人干预,那么它最少死n/2个人,向下取整。也就是一个人如果不杀人就只能被杀死,对于奇数个点的环你杀了人也可能被杀死。最多就是总人数-1。
- 一个环如果有人干预,那么它最多就是全部被杀,最小在下面说。
- 如果一个人没死,那么他指向的那个人就一定会死,如果指向那个人的人都死了,那么他就可以活下来,这一点在环中同样适用。
为了方便,我们直接去求活着的人数,反正人不是活着就是死了废话。
先说死的人最少,那么入度为0的人一定活下来了,因此我们用队列慢慢往上爬即可,只要队列中的人他所杀的人要杀的人没人杀他了,那么他也可以入队。至于没人指向的环嘛,上面说了。
死的最多的人就是让人们从树顶从上往下开枪,直到叶子节点所以答案就为缩完点后入度为0的点。
我打完之后只过了一个点,因为这题有两个坑点,至少对我来说是这样的。
第一,指向这个点的点可以有好几个,但这个点的出度只有一个,虽然是废话,但在你判断谁存活的时候需要先判断一下,你目前队首的这个点所指向的点是否已经被杀掉,否则错炸了。
第二,也是卡住了无数英雄好汉的点,如何判断某个环是否已被用队列访问,开个bool记录看似容易,然而在哪里打标记就是大坑了,对于WA的童鞋们可以试一下这个点:
4
2 3 2 3
希望能帮到你们,在这个点中,环中每个人都死了,但如果你在将某个元素塞进队列时才将它打上标记你会发现实际已经访问了的环并未被打上标记,因此,正解是每个被塞进队列的人他所指向的人和他所指向的人所指向的人都要被打上标记。
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
int n,tt[],st[],top;
bool rz[],rz2[];
int dfn[],low[],zz1;
int zz2,belong[],sum[];
void tar(int x){
zz1++;
top++;
st[top]=x;
rz[x]=rz2[x]=;
dfn[x]=low[x]=zz1;
if(!rz2[tt[x]])
{
tar(tt[x]);
low[x]=min(low[x],low[tt[x]]);
}
else if(rz[tt[x]])
{
low[x]=min(low[x],dfn[tt[x]]);
}
if(dfn[x]==low[x])
{
int v;
zz2++;
do{
v=st[top];
top--;
rz[v]=;
sum[zz2]++;
belong[v]=zz2;
}while(dfn[v]!=low[v]);
}
}
int a[],zz3,rd2[],be[];
bool fw[],js[];
int rd[],ans1,ans2;
queue<int> q1;
int main(){
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d",&tt[i]);
rd[tt[i]]++;
}
for(int i=;i<=n;i++)
if(!rz2[i])
tar(i);
for(int i=;i<=n;i++)
{
if(rd[i]==)
{
ans1++;
q1.push(i);
fw[belong[i]]=;
}
}
while(!q1.empty())
{
int x=q1.front();
q1.pop();
fw[belong[tt[x]]]=;
if(!js[tt[x]])
{
js[tt[x]]=;
rd[tt[tt[x]]]--;
if(rd[tt[tt[x]]]==)
{
ans1++;
fw[belong[tt[tt[x]]]]=;
q1.push(tt[tt[x]]);
}
}
}
for(int i=;i<=zz2;i++)
{
if(!fw[i])
{
ans1+=sum[i]/;
}
}
for(int i=;i<=n;i++)
{
if(belong[tt[i]]!=belong[i]||tt[i]==i)
{
rd2[belong[tt[i]]]++;
}
}
for(int i=;i<=zz2;i++)
{
if(!rd2[i])
ans2++;
}
printf("%d %d",n-ans1,n-ans2);
return ;
}
对了,这道题打法不只一种,有兴趣的读者可以试一试别的方法,DP已被验证可行,贪心据说也可以,希望读者不要拘泥于一种打法。