BZOJ1124 POI2008枪战Maf(环套树+贪心)

  每个点出度都为1,可以发现这张图其实是个环套树森林,树中儿子指向父亲,环上边同向。

  首先自环肯定是没救的,先抬出去。

  要使死亡人数最多的话,显然若一个点入度为0其不会死亡,而一个孤立的环至少会留下一个点。对于环套树,若某个点有子树,可以以瞄准它的点为起点,每个点都被在环上瞄准他的点所击中。这样就剩下了很多棵树,除叶子节点的点都会死亡。

  死亡人数最少似乎同样可以贪心,虽然我没这么写。可以发现最后存活下来的人之间一定不存在瞄准关系,否则必有一个死亡。并且只要最后存活下来的人之间不存在瞄准关系(且不被瞄准的存活),一定有方案使这些人最终存活下来,对于一个连通块人的死亡只要按照拓扑逆序开枪即可(使孤立环全部死亡是办不到的,但显然要使死亡人数最少我们不会这样干)。于是求一下环套树的包含所有叶子节点的最大独立集即可。

  细节挺多,在luoguA了,bzoj跑了好长时间之后wa掉了,不知道哪写挂了啊。

  upd:发现是一些奇怪的地方爆了int……现在A掉辣!

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
    int x=0,f=1;char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
    while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;
}
#define N 1000010
int n,a[N],id[N],degree[N],dfn[N],low[N],stk[N],set[N],size[N];
int top=0,cnt=0,t=0;
long long ans1=0,ans2=0,f[N][2],g[N][2][2];
bool flag[N],isroot[N];
int p[N];
struct data{int to,nxt;
}edge[N];
void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;}
void tarjan(int k)
{
    dfn[k]=low[k]=++cnt;
    stk[++top]=k;flag[k]=1;
    if (a[a[k]]!=a[k])
    if (!dfn[a[k]]) tarjan(a[k]),low[k]=min(low[k],low[a[k]]);
    else if (flag[a[k]]) low[k]=min(low[k],dfn[a[k]]);
    if (dfn[k]==low[k])
    {
        t++;
        while (stk[top]!=k)
        {
            set[stk[top]]=t;
            size[t]++;
            flag[stk[top]]=0;
            top--;
        }
        set[k]=t;size[t]++;flag[k]=0;top--;
    }
}
void dfs(int k)
{
    f[k][1]=1,f[k][0]=0;
    if (!degree[k]) f[k][0]=-n;
    for (int i=p[k];i;i=edge[i].nxt)
    if (size[set[edge[i].to]]==1)
    {
        dfs(edge[i].to);
        f[k][0]+=max(f[edge[i].to][0],f[edge[i].to][1]);
        f[k][1]+=f[edge[i].to][0];
    }
}
int main()
{
    n=read();
    for (int i=1;i<=n;i++) 
    {
        a[i]=read();
        if (a[i]==i) ans1++;
        else degree[a[i]]++;
    }
    for (int i=1;i<=n;i++)
    if (!dfn[i]&&a[i]!=i) tarjan(i);
    for (int i=1;i<=n;i++)
    if (degree[i]&&size[set[i]]==1) ans1++;
    memset(flag,0,sizeof(flag));
    for (int i=1;i<=n;i++)
    if (size[set[i]]>1&&degree[i]>1) flag[set[i]]=1;
    for (int i=1;i<=t;i++)
    if (size[i]>1) ans1+=size[i]-1+flag[i];
    t=0;
    for (int i=1;i<=n;i++)
    if (a[i]!=i) addedge(a[i],i);
    for (int i=1;i<=n;i++)
    if (size[set[i]]>1||a[a[i]]==a[i]&&a[i]!=i) isroot[i]=1;
    for (int i=1;i<=n;i++)
    if (isroot[i]) dfs(i);
    memset(dfn,0,sizeof(dfn));
    for (int i=1;i<=n;i++)
    if (isroot[i]&&!dfn[i]) 
    if (a[a[i]]==a[i]) ans2+=max(f[i][0],f[i][1]);
    else
    {
        int x=i,t=0;
        while (a[x]!=i) dfn[x=a[x]]=1,id[++t]=x;
        id[++t]=i;dfn[i]=1;
        for (int j=1;j<=t;j++) g[i][0][0]=g[i][0][1]=g[i][1][0]=g[i][1][1]=0;
        g[1][0][0]=f[id[1]][0],g[1][1][1]=f[id[1]][1];
        g[1][1][0]=g[1][0][1]=-n;
        for (int j=2;j<=t;j++)
        {
            g[j][0][0]=max(g[j-1][0][0],g[j-1][1][0])+f[id[j]][0];
            g[j][0][1]=max(g[j-1][0][1],g[j-1][1][1])+f[id[j]][0];
            g[j][1][0]=g[j-1][0][0]+f[id[j]][1];
            g[j][1][1]=g[j-1][0][1]+f[id[j]][1];
        }
        ans2+=max(g[t][0][0],max(g[t][0][1],g[t][1][0]));
    }
    ans2=n-ans2;
    cout<<ans2<<' '<<ans1;
    return 0;
}

 

转载于:https://www.cnblogs.com/Gloid/p/9403624.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值