多的先不说,上题:
指认坏人
n个人中有好人,也有坏人。坏人知道每个人的身份,好人不知道。
现在每个人都指认了一个坏人(没有人会指认自己为坏人),当然,好人基本是乱猜的,而坏人则全部指认的是好人。
那么在不知道谁是坏人的情况下,最多可能有多少坏人。
【输入格式】
输入的第1行包含一个整数n,表示有n个人。这n个人标号为1到n。接下来有n行,每行一个数,其中的第i行表示被第i个人指认的坏人编号。没有谁会指认自己为坏人。
【输出格式】
仅输出一个整数,表示最多可能的坏人的数量。
【输入样例】
【样例1】
3
2
1
1
【样例2】
3
2
3
1
【样例3】
7
3
3
4
5
6
4
4
【输出样例】
【样例1】
2
【样例2】
1
【样例3】
4
【样例解释】
第一个样例解释:杀手可能为2和3;
第二个样例解释:杀手可能是任何1个人,但不可能多于1个,否则杀手就会指认自己人了。
【数据范围】
40%的数据n<25.
80%的数据n<=2000
100%的数据 2<=N<=500000
一开始看到还以为是并查集的模型,但是每两个人并不存在此好则彼坏的情况,和并查集一点也扯不上关系。
如果i指认j的话连有向边i->j,则每个点一定只有一个出度。
对于题干分析我们发现,坏人只会指认好人,指认坏人的也只能是好人,那么意味着如果点i为坏人,那么和i有变相邻的点(不管方向如何)都不能是坏人。那么是不是就可以把有向边当成是无向边。
如果所有人的关系只是构成一颗树的话,那么该题的最优解就是一颗树的最大独立集。但是这里每个人之间不应定都连通,而且的所有构成的连通分量都是以x个点,x条边的形式存在(基环外向树)。
那么问题就转化为求基环外向树森林的最大独立集。发现基环外向树上最多且必须出现一个环,而环又是我们难以处理的,所以我们可以找出环上的一条边,然后删掉该边求最优解。对于一条边上的两个点s,t,不能同时都选,所以枚举哪个点一定不选,相当于提前满足删掉的这条边的要求,再跑树上的最大独立集,两者中较大的就是最优解。(但是这里好像就只能用动态规划而不能用贪心了)
一个需要注意的地方就是如何保证删除那条指定的边。
可以举一个简单的例子:如果只有1 2 两个人,1指认2,2指认1。
如果dfs的时候不能只要发现一条边是i<->j的,就continue。这样做会导致不管是1不选还是2不选,得到的答案都是0。对于这样互选的情况,我们考虑标记找环时的边i->j的编号为x,只有在当前这条边表示的是i-j的那条x边时才continue。由于无向图边存储了两次,所以x和x+1/x-1是同一条边。(例如:1和2,3和4……)
P.S:其实也可以在建立树的时候将每条双向边编号,判断的时候就没有这么麻烦了
另附一下树的独立集的递归方程:
f[i][0]表示不选i时,以i为根的树的最大独立集
f[i][1]表示选i时,以i为跟的树的最大独立集
f[i][0]=sum{ max(f[j][0],f[j][1]} |j是i的儿子}
f[i][1]=sum{ f[j][0] |j是i的儿子}+1
#include
#include
#include
#include
#include
using namespace std;
const int maxn=500005;
int np=0,first[maxn],n,fa[maxn],s,t,x,ans,sum,vis[maxn],f[maxn][2];
struct edge
{
int to,next;
}e[2*maxn];
void addedge(int u,int v)
{
e[++np]=(edge){v,first[u]};
first[u]=np;
}
void dfs(int i,int f)
{
vis[i]=1;
for(int p=first[i];p;p=e[p].next)
{
int j=e[p].to;
if(j==f) continue;
if(vis[j]) s=i,t=j,x=p;
else dfs(j,i);
}
}
void dfs_(int i,int fa)
{
f[i][1]=1;
for(int p=first[i];p;p=e[p].next)
{
int j=e[p].to;
if(j==fa || x==p) continue;
if(p%2==0 && (p^1)-2==x) continue;//是不是标记的那条边
if(p%2==1 && (p^1)+2==x) continue;
dfs_(j,i);
f[i][1]+=f[j][0];
f[i][0]+=max(f[j][0],f[j][1]);
}
}
int main()
{
freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&t);
addedge(i,t);
addedge(t,i);
}
for(int i=1;i<=n;i++)//形成的图一定是一个基环外向树的若干连通分量
if(!vis[i])
{
dfs(i,0);
dfs_(s,0);
sum=f[s][0];
memset(f,0,sizeof(f));
dfs_(t,0);
ans+=max(sum,f[t][0]);
memset(f,0,sizeof(f));
}
printf("%d",ans);
return 0;
}
再来看下另一道(加强版)基环外向树的题目:
【zjoi2008】骑士
Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。
最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。
骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。
战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
【输入格式】
第一行包含一个正整数N,描述骑士团的人数。
接下来N行,每行两个正整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。
【输出格式】
包含一行,包含一个整数,表示你所选出的骑士军团的战斗力。
【输入样例】
3
10 2
20 3
30 1
【输出样例】
30
【数据范围】
对于100%的测试数据,满足N ≤ 1 000 000,每名骑士的战斗力都是不大于 1 000 000的正整数。
分析和上面一道的一致,差别就在于点带权。
直接把f[i][1]的初值附成w[i]就好了。
不粘代码了。。