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

本文介绍了一种通过图论中的染色方法预测未来的有趣故事背景下的算法实现。针对给定的图,在删除特定边后判断是否能成功进行二染色,并提供了一种线性时间复杂度内的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

从前有一个善良的精灵。

一天,一个年轻人B找到她并请他预言他的未来。这个精灵透过他的水晶球看到这个年轻人将要遇见一个非常美丽的公主并要娶她。。精灵在一张纸上画了N个点并用一些线段将他们连起来,每条线段两端连着不同的点。画完了之后,精灵让年轻人去除一条线段。然后精灵尝试将每个点用红色或者蓝色进行染色,同时使得那里没有一条线段的两端是相同的颜色。如果精灵能够成功染色,这个预言就能成真。

年轻人想要遇见那位公主,因此他请求你去帮助他,找到所有的删除之后能对图进行成功染色的线段。

Input

输入文件中的第一行为两个整数N,M,分别表示点的个数和线段的条数。

接下来的M行,描述了每条线段,每行有两个整数v,u(1<=v,u<=n),表示线段两端的点的标号。注意:没有线段会重复出现。

Output

输出文件中的第一行为一个整数K,即删除之后能对点进行染色的线段的个数。

接下来的一行包含K个从小到大的数字,数字之间用一个空格隔开,行末没有空格,表示满足条件的线段的标号。每条线段的标号为读入顺序的标号。

Sample Input

输入1:

4 4

1 2

1 3

2 4

3 4

输入2:

4 5

1 2

2 3

3 4

4 1

1 3

Sample Output

输出1:

4

1 2 3 4

输出2:

1

5

Data Constraint

对于50%的数据,满足:N,M<=1000;

对于100%的数据,满足:N,M<=10000。

Solution

  • 首先,设 fodd[i]feven[i]godd[x]geven[x]sum

  • fodd[i] 表示 编号为 i 的边 被多少个 奇数环 包含;

  • feven[i] 表示 编号为 i 的边 被多少个 偶数环 包含;

  • godd[x] 表示 编号为 x 的点 的奇数环标记;

  • geven[i] 表示 编号为 x 的点 的偶数环标记;

  • sum 表示奇数环的总个数。(意义用法稍后讲解)

  • 众所周知,如果有奇数环就要果断删边,偶数环则应保留。

    • 所以说,一条边 i 可以被删,当且仅当 fodd[i]=sum 并且 feven[i]=0

    • 即:被所有奇数环包含,没有偶数环经过。

    • 那么问题就转换成如何在线性时间复杂度内处理出 F 数组。

    • 考虑把图当做树来看,处理出深度数组 dep[i]

    • 若当前点为 u ,连向 v ,而 dep[v] 有值且小于 dep[u] ,则说明出现了一个环。

    • 那么用 dep[u]dep[v] (环内其他点的个数) 的奇偶性可以知道这是一个奇数环还是偶数环。

    • 之后累加 fodd/even[i],可以则累加 sum ,接着:(关键!

    • godd/even[u]++godd/even[v] ,即打一个 Tag 标记(类似差分约束),如下图:

    Solution

    • 那么如果 sum=0 (没有奇数环)就所有边都可以删,全部输出啦!

    • 紧接着,再做一遍 DFS ,像统计子节点个数一样从下至上累加 Tag 标记;

    • 中途更改 F 数组即可,注意奇偶对应。如下图:

    Solution

    • 那么计算答案即可!

    Code

    #include<cstdio>
    using namespace std;
    const int N=10001;
    int tot,sum;
    int first[N],next[N*2],en[N*2],w[N*2];
    int fodd[N],feven[N],godd[N],geven[N];
    int ans[N],dep[N];
    bool bz[N*2],vis[N];
    inline int read()
    {
        int data=0; char ch=0;
        while(ch<'0' || ch>'9') ch=getchar();
        while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();
        return data;
    }
    inline void insert(int x,int y,int z)
    {
        next[++tot]=first[x];
        first[x]=tot;
        en[tot]=y;
        w[tot]=z;
    }
    inline void dfs(int x,int fa)
    {
        dep[x]=dep[fa]+1;
        for(int i=first[x];i;i=next[i])
            if(en[i]!=fa)
            {
                if(!dep[en[i]])
                {
                    bz[i]=true;
                    dfs(en[i],x);
                }else
                    if(dep[en[i]]<dep[x])//Link
                        if((dep[x]-dep[en[i]])&1)
                        {
                            geven[en[i]]--;
                            geven[x]++;
                            feven[w[i]]=1;//Even
                        }else
                        {
                            godd[en[i]]--;
                            godd[x]++;
                            fodd[w[i]]=1;//Odd
                            sum++;
                        }
            }
    }
    inline void find(int x)
    {
        vis[x]=true;
        for(int i=first[x];i;i=next[i])
            if(bz[i])
            {
                find(en[i]);
                fodd[w[i]]=godd[en[i]];
                feven[w[i]]=geven[en[i]];
                godd[x]+=godd[en[i]];
                geven[x]+=geven[en[i]];
            }
    }
    int main()
    {
        int n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read();
            insert(x,y,i);
            insert(y,x,i);
        }
        for(int i=1;i<=n;i++)
            if(!dep[i]) dfs(i,0);
        if(!sum)
        {
            printf("%d\n",m);
            for(int i=1;i<=m;i++) printf("%d ",i);
            return 0;
        }
        for(int i=1;i<=n;i++)
            if(!vis[i]) find(i);
        for(int i=1;i<=m;i++)
            if(!feven[i] && fodd[i]==sum) ans[++ans[0]]=i;
        printf("%d\n",ans[0]);
        for(int i=1;i<=ans[0];i++) printf("%d ",ans[i]);
        return 0;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值