JZOJ 3506. 【NOIP2013模拟11.4A组】善良的精灵

题目

从前有一个善良的精灵。
一天,一个年轻人B找到她并请他预言他的未来。这个精灵透过他的水晶球看到这个年轻人将要遇见一个非常美丽的公主并要娶她。精灵在一张纸上画了N个点并用一些线段将他们连起来,每条线段两端连着不同的点。画完了之后,精灵让年轻人去除一条线段。然后精灵尝试将每个点用红色或者蓝色进行染色,同时使得那里没有一条线段的两端是相同的颜色。如果精灵能够成功染色,这个预言就能成真。
年轻人想要遇见那位公主,因此他请求你去帮助他,找到所有的删除之后能对图进行成功染色的线段。
对于100%的数据,满足:N,M<=10000。

题解

可以很快地想到二分图的性质(二分图中的所有回路边数都为偶数)。
假设有一点u,那么我们开始连边,假设连到了v点,此时又有一条边(u,v)。
设temp为这个环的点的个数。temp=deep[v]-deep[u]。
如果temp为奇数,那么此环为奇数环,否则为偶数环。
如果此环为奇数环,那么这条边(u,v)可能要被删掉。
设f0[x]表示边x在多少个偶数环中。f1[x]表示边x在多少个奇数环中。
设g0[x],g1[x]为关于点x的标记。
首先找环的时候,如果找到一个环,在相应的f中环头u的g–,环尾v的g++。
对于构成环的最后一条边t(就是之前说的那条边(u,v)),f[t]=1.
再次dfs将g中的标记上传:
这里写图片描述
首先f[x]=g[v]。将v的标记上传到x。
然后g[father[v]]+=g[v]。
v=father[v]。
最后,如果没有奇数环,则所有边都可以删。如果所有的奇数环都经过该边,且没有偶数环经过该边,则这条边可以删。

代码(未验)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define N 10005
#define fo(i,a,b) for(i=a;i<=b;i++)
#define eg(i,x) for(i=head[x];i;i=next[i])
using namespace std;
int n,m,i,j,k,l,tot,ans,an[N],x,y,cnt;
int f0[N*2],f1[N*2],g0[N],g1[N],deep[N];
int head[N],go[N*2],next[N*2],v[N*2];
bool bz[N*2],b1[N];
void lb(int x,int y,int z)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
    v[tot]=z;//将边的序号放入数组模拟链表中,可以很快读出这条边原来的编号。这很巧妙 
}
void dfs(int x,int fa)
{
    int i,now,temp;
    deep[x]=deep[fa]+1;
    eg(i,x)
    {
        now=go[i];
        if (now==fa) continue;
        if (deep[now]==-1)
        {
            bz[i]=1;
            dfs(now,x);
        } else
        if (deep[now]<deep[x])
        {
            temp=deep[x]-deep[now];
            if (temp%2) g0[now]--,g0[x]++,f0[v[i]]=1;
                   else g1[now]--,g1[x]++,f1[v[i]]=1,cnt++;
        }
    }
}
void df2(int x,int fa)
{
    int i,now;
    b1[x]=1;
    eg(i,x)
    {
        now=go[i];
        if (bz[i])
        {
            df2(now,x);
            f0[v[i]]=g0[now];f1[v[i]]=g1[now];//将边连着的点的g更新到边的f 
            g0[x]+=g0[now];g1[x]+=g1[now];//点的g上传给点的父亲 
        }
    }
}
int main()
{
    freopen("fairy.in","r",stdin);
    freopen("fairy.out","w",stdout);
    memset(deep,255,sizeof(deep));
    scanf("%d%d",&n,&m);
    fo(i,1,m)
    {
        scanf("%d%d",&x,&y);
        lb(x,y,i);
        lb(y,x,i);
    }
    fo(i,1,n) if (deep[i]==-1) dfs(i,0);
    if (!cnt)
    {
        printf("%d\n",m);
        fo(i,1,m) printf("%d ",i);
        return 0;
    }
    fo(i,1,n) if (!b1[i]) df2(i,0);
    ans=0;
    fo(i,1,m)
        if (!f0[i] && f1[i]==cnt) an[++ans]=i;
    printf("%d\n",ans);
    fo(i,1,ans) printf("%d ",an[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值