CQYZ【复赛模拟题】指认坏人+【zjoi2008】骑士

多的先不说,上题:


指认坏人

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<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
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]就好了。

不粘代码了。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值