BZOJ3037: 创世纪

来源:互联网 发布:淘宝赚钱助手怎么用 编辑:程序博客网 时间:2024/04/28 20:55

题目大意:给定一个有向图,每个点出度为1,让你将这些点分为两个集合x和y,使得对于每一个y集合内的点都存在一个x集合内的点指向它,并且使得y集合尽可能大。
原图给的是一些内向基环树,显然要求的是一个最小支配集@poj3659。内向的基环树我们不好处理,考虑将这颗树反向,变成了外向基环树,这样我们可以随便找到环上的一个点以及指向它的点(首先每个联通快一定有环,但其实没有环的话就是正常的树了),不妨设为y->x,枚举x选择还是不选择,如果x选了,那么y就有人支配了,以x为根做正常的最小支配集即可。
做最小值配集的时候有一个小技巧tmp,有兴趣可以阅读下…
(还有一种直接贪心乱搞的做法在下面…)

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>//by:MirrorGrayusing namespace std;const int N=1111111;int tot=-1,head[N],ver[N],nxt[N];int root,f[N],g[N],fa[N],vis[N],ban[N];void add(int x,int y){    nxt[++tot]=head[x];    head[x]=tot;    ver[tot]=y;}int find(int x){    vis[x]=true;    return vis[fa[x]]?x:find(fa[x]);}void dfs(int x){    vis[x]=true;f[x]=1;g[x]=0;    int tmp=0x3f3f3f3f;    for(int i=head[x];~i;i=nxt[i])if(ver[i]!=root){        dfs(ver[i]);        f[x]+=min(f[ver[i]],g[ver[i]]);        g[x]+=min(f[ver[i]],g[ver[i]]);        tmp=min(tmp,f[ver[i]]-g[ver[i]]);    }    if(!ban[x])g[x]+=max(0,tmp);//强制选了root,fa[root]就有人管理了}int main(){    memset(head,-1,sizeof(head));    int n;scanf("%d",&n);    for(int i=1;i<=n;i++)scanf("%d",&fa[i]),add(fa[i],i);    int ans=0;    for(int i=1;i<=n;i++)if(!vis[i]){        int x=find(i);        ban[fa[root=x]]=true;        dfs(x);int tmp=f[x];        ban[fa[x]]=false;        dfs(x);tmp=min(tmp,g[x]);        ans+=tmp;    }    printf("%d\n",n-ans);    return 0;}

考虑直接在原来的内向基环树上贪心,首先所有入度为0的点一定是要选的,他们所指向的点都不用选了,只要还没有进环,我们都可以本着只要指向它的点选了就不选它的贪心策略来处理,这个东西可以拓扑排序搞一下,最后处理环,现在有一个环,环上有一些点不用必须被支配,使得每个点要么选择要么被支配,乱搞吧…>_<…
(wa了无数遍发现我输出了最小支配集而不是最大被支配集…>_<…样例还能过…考场上小心啊…)

#include<queue>#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>//by:MirrorGrayusing namespace std;const int N=1111111;int cnt;int top,sta[N];int num[N],point[N],c[N];int in[N],size[N],vis[N],mark[N],p[N];struct Union_Find_Set{    int fa[N],size[N];    Union_Find_Set(){        for(int i=0;i<N;i++)fa[i]=i,size[i]=1;    }    int find(int x){        return fa[x]==x?x:fa[x]=find(fa[x]);    }    void merge(int a,int b){        a=find(a),b=find(b);        if(size[a]>size[b])swap(a,b);        if(a==b)return ;fa[a]=b;    }}ufs;queue <int> q;void find_circle(int x){    vis[x]++;    if(vis[x]==2)sta[++top]=x;    if(vis[p[x]]!=2)find_circle(p[x]);}int main(){    int n;scanf("%d",&n);    for(int i=1;i<=n;i++){        scanf("%d",&p[i]),in[p[i]]++;        ufs.merge(p[i],i);    }    for(int i=1;i<=n;i++)if(!mark[ufs.find(i)]){        mark[ufs.find(i)]=true;        find_circle(i);size[++cnt]=top;        while(top)num[sta[top--]]=cnt;    }    int ans=0;    for(int i=1;i<=n;i++)if(!in[i])q.push(i);    while(!q.empty()){        int x=q.front();q.pop();        bool fg=false;        if(!c[x])ans++,fg=true,c[x]=true;        int pos=p[x];in[pos]--;        c[pos]|=fg;        if(num[pos]&&c[pos])point[num[pos]]=pos;        if(!in[pos]&&!num[pos])q.push(pos);    }    memset(mark,false,sizeof(mark));    for(int i=1;i<=n;i++)if(num[i]&&!mark[num[i]]){        mark[num[i]]=true;        if(!point[num[i]])ans+=(size[num[i]]+1)>>1;        else{            int pos=p[point[num[i]]];            while(pos!=point[num[i]]){                if(!c[pos])ans++,c[p[pos]]=true;                pos=p[pos];            }        }    }    printf("%d\n",n-ans);    return 0;}//有向图最麻烦的是不能保证从任意一个点开始dfs都能遍历整个联通快 /*106 5 1 2 1 1 4 1 7 7*/
0 0
原创粉丝点击