记录编号 318842 评测结果 AAAAAAAAAA
题目名称 膜法师 最终得分 100
用户昵称 Gravatarliu_runda 是否通过 通过
代码语言 C++ 运行时间 2.110 s
提交时间 2016-10-09 21:38:07 内存使用 83.28 MiB
显示代码纯文本
/*
分析问题:将“膜与被膜”的关系建成边,我们可以认为第一问是图上的最大独立集问题。
其中,要求有自环的人必须被选中,不被膜的人必须被选中.
这个图是一堆环,每个环上长了一堆树,所以我们对环上的每棵树跑树上最大独立集,再在环上跑一个线性DP...
第二问自己推推结论。 
*/
#include<cstdio>
#include<cassert>
#include<cctype>
#include<vector>
#include<algorithm>
using namespace std;
char ch;
void read(int &x){
    while(ch=getchar(),!isdigit(ch));
    x=ch-'0';
    while(ch=getchar(),isdigit(ch))x=x*10+ch-'0';
}
const int maxn=1000005;
struct edge{
    int to,next;
}lst[maxn];int len=1;
int first[maxn];
void addedge(int a,int b){
    lst[len].to=b;
    lst[len].next=first[a];
    first[a]=len++;
}
int to[maxn];
int T;
int dfn[maxn],low[maxn],s[maxn],top,belong[maxn],cnt,indeg[maxn],sz[maxn];
bool ins[maxn];
vector<int> scc[maxn];
inline void tarjan(int x){
    dfn[x]=low[x]=++T;
    s[top++]=x;ins[x]=true;
    for(int pt=first[x];pt;pt=lst[pt].next){
        if(!dfn[lst[pt].to]){
            tarjan(lst[pt].to);
            if(low[lst[pt].to]<low[x])low[x]=low[lst[pt].to];
        }else if(ins[lst[pt].to]&&dfn[lst[pt].to]<low[x])low[x]=dfn[lst[pt].to];
    }
    if(low[x]==dfn[x]){
        ++cnt;
        do{
            ins[s[--top]]=false;
            belong[s[top]]=cnt;
            sz[cnt]++;
            scc[cnt].push_back(s[top]);
        }while(s[top]!=x);
    }
}
bool not_mo[maxn];
bool dpdone[maxn];
int f[2][maxn];//f[0][i]:不选择i时,以i为根的子树的最大独立集 f[1][i]:选择i. "选择某个点"对应”这个人直到最后也不被膜“ 
int q[maxn];int head,tail;
int max(int a,int b){
    return a>b?a:b;
}
void treedp(int x){//非递归式树规,先求出bfs序再按bfs序倒序处理 
    head=tail=0;
    q[tail++]=x;
    while(head!=tail){
        x=q[head++];dpdone[x]=true;
        for(int pt=first[x];pt;pt=lst[pt].next){
            if(belong[x]==belong[lst[pt].to])continue;
            if(!dpdone[lst[pt].to]){
                q[tail++]=lst[pt].to;
            }
        }
    }
    for(int i=tail-1;i>=0;--i){
        x=q[i];
        if(not_mo[x]){//最后必然不会被膜,因此只有f[1][x],没有f[0][x],f[0][x]设为-inf 
            f[0][x]=-0x7f7f7f7f;
            f[1][x]=1;
            continue;
        }
        f[1][x]=1;
        bool flag=false;
        for(int pt=first[x];pt;pt=lst[pt].next){
            if(belong[x]==belong[lst[pt].to])continue;
            if(not_mo[lst[pt].to]){//发现一个必然不会被膜的人要膜x,那么x必然会被膜,只有f[0][x],没有f[1][x] 
                flag=true;
            }
            f[1][x]+=f[0][lst[pt].to];
            f[0][x]+=max(f[0][lst[pt].to],f[1][lst[pt].to]);
        }
        if(flag)f[1][x]=-0x7f7f7f7f;//这个f[1][x]如果被用到,一定会先和f[0][x]取较大值,因此不会相加溢出 
    }
}
int delta[maxn];
int g[2][2][maxn];//第一维:开头的数是否选择 第二维:当前处理的数是否选择(对于f[][][i]就是第i个数) 
int lineardp(int sccnum){//编号为sccnum的环上最多能有多少人不被膜  
    int ans=0,lim=sz[sccnum];
    for(int i=0;i<lim;++i){
        ans+=f[0][scc[sccnum][i]];
        if(f[1][scc[sccnum][i]]<0)delta[i]=0;
        else delta[i]=f[1][scc[sccnum][i]]-f[0][scc[sccnum][i]];//环上每个位置可以选f[0]或f[1],相邻位置不能同时选f[1],我们先全都选f[0]
        //把i从f[0]换成f[1]k可以得到delta[i],环上两个相邻的delta[i]不能同时得到,接下来用线性dp可以求出最大可获得的delta[i]之和 
    }
    g[0][0][0]=0;g[1][1][0]=delta[0];g[1][0][0]=-0x3f3f3f3f;g[0][1][0]=-0x3f3f3f3f;
    for(int i=1;i<lim;++i){
        g[0][0][i]=max(g[0][1][i-1],g[0][0][i-1]);
        g[1][0][i]=max(g[1][1][i-1],g[1][0][i-1]);
        g[0][1][i]=g[0][0][i-1]+delta[i];
        g[1][1][i]=g[1][0][i-1]+delta[i];
    }//f[1][1][sz[sccnum]-1]为非法状态 
    return ans+max(g[0][0][lim-1],max(g[0][1][lim-1],g[1][0][lim-1]));//一定要记得在dp的最佳状态上加上一开始的ans 
}
int main(){
    freopen("maf.in","r",stdin);
    freopen("maf.out","w",stdout);
    int __size__=128<<20;
    char *__p__=(char*)malloc(__size__)+__size__;
    __asm__("movl %0, %%esp\n"::"r"(__p__));

    int n;read(n);
    int ans1=n,ans2=n;//ans1:被膜的人最少  ans2:被膜的人最多 
    for(int i=1;i<=n;++i){
        read(to[i]);
        if(to[i]==i){
            continue;
        }
        addedge(to[i],i);
    }
    for(int i=1;i<=n;++i){
        if(!dfn[i]&&to[i]!=i){
            tarjan(i);
        }
    }
    //被膜的人最多,采用总人数-无法被膜的人数。零入度的点无法被膜,每个孤立的环会剩下一个点无法被膜 
    for(int i=1;i<=n;++i){
        if(belong[i]!=belong[to[i]])indeg[belong[to[i]]]++;
    }
    for(int i=1;i<=cnt;++i){
        if(indeg[i]==0)ans2--;//indeg[i]==0:孤立的环/零入度的点,均会剩下一个人 
    }
    for(int i=1;i<=n;++i){
        if(sz[belong[i]]==1&&indeg[belong[i]]==0)not_mo[i]=true;
    }
    for(int i=1;i<=n;++i){
        if(!dpdone[i])treedp(i);
    }
    //for(int i=1;i<=n;++i){
//        printf("%d %d\n",f[0][i],f[1][i]);
//    }
    //ans2:用总人数减去最多的不被膜人数
    for(int i=1;i<=n;++i){
        if(to[i]==i){
            ans1-=f[0][i]; 
        }
    }
    for(int i=1;i<=cnt;++i){
        if(sz[i]>1){
            ans1-=lineardp(i); 
        }
    }
    printf("%d %d\n",ans1,ans2);//while(1); 
    fclose(stdin);fclose(stdout);
    return 0;
}