CF E. Moment of Bloom Codeforces Round #749

题意

给定n个节点m条边的无向连通图以及q个查询,一开始所有边的权值为0,每个查询给定两个点u和v,要求选择从u到v的一条简单路径(路径上每个点不经过超过1次),使得这条路径上的权值都+1,最后得到的图中所有边的权值都为偶数,问是否能有q条路径满足条件,满足的话输出路径,不满足则输出需要添加的查询数。

思路

首先证明以下两点:
1.某个点在q次查询中如果出现奇数次,那么这张图一定无解。
2.所有点在q次查询中出现次数都为偶数次,那么这张图一定有解。
首先证明1,如果某个点u在查询中出现奇数次,因为对于每个查询的路径不经过同一个点超过1次,所以u点相邻的点肯定有一条边对应的权值为奇数。
接着证明2,如果所有点在查询中出现偶数次,由于原图是连通图,我们可以先从原图中构造一颗生成树,如果这颗生成树有解,那么原图一定有解,因为生成树的解一定能在原图中找到。在树中任意两点间的简单路径一定唯一,查询中每个点出现偶数次,那么查询一定是构成若干个环的形式比如 x y ,y z, x z,可以看为x->y->z->x这样的环,但其实在树中走的是同一条路径两次,正好让路径上的权值正好为偶数次,若干个这样的路径环同理。
根据上面两个证明可以得出如下结论,如果每个点在查询中出现的次数为偶数,只需要构造一颗生成树,在生成树中直接输出对应的路径就好了。
如果某个点在查询中出现的次数为奇数,需要通过添加查询将奇数变为偶数,如果在查询中出现次数为奇数次的点有k个,可以通过添加一个查询更改两个点的奇偶性,根据这个性质构造的图中奇数点的个数一定为偶数个,所以所需要增加的查询次数为k/2次。

代码

#include<bits/stdc++.h>
using namespace std;
int d[400000];//每个点在查询中出现的次数
int n,m,q;//点数,边数和查询数
int fa[400000];//并查集数组
int head[400000],net[1000000],v[1000000];//存储生成树
int tot;//生成树中边的数量
int numofodd;//在查询中出现奇数次点的数量
int x[400000],y[400000];//查询
int dep[400000];//对应节点的深度
int L[400000],R[400000];
int pre[400000];//对应节点在生成树中的父亲节点
int find(int x)//并查集集合查询操作
{
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
void add(int x,int y)//添加边操作
{
   v[++tot]=y,net[tot]=head[x],head[x]=tot;
}
void init()
{
    numofodd=0;
    pre[1]=0;
    for(int i=1;i<=n;i++)
    fa[i]=i;
}
void bfs(int x,int deep)//求深度
{  
    dep[x]=deep;
    deque<int>q;
    q.push_back(x);
    while(q.size())
    {
        x=q.front();
        q.pop_front();
        for(int i=head[x];i;i=net[i])
        {
            int y=v[i];
            if(dep[y])
            continue;
            q.push_back(y);
            pre[y]=x;
            dep[y]=dep[x]+1;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    init();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        int tx=find(x);
        int ty=find(y);
        if(tx!=ty)
        {
            add(x,y);
            add(y,x);
            fa[tx]=ty;
        }
    }
    bfs(1,1);//以1号节点为根节点求深度和每个点的父亲节点
    
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d",&x[i],&y[i]);
        d[x[i]]++;
        d[y[i]]++;
    }
    for(int i=1;i<=n;i++)
    {
        if(d[i]&1)
        numofodd++;
    }
    if(!numofodd)
    {
        printf("YES\n");
        for(int i=1;i<=q;i++)//因为求了深度和父亲节点,像lca那样子往上找
        {
            int a=x[i],b=y[i];
            int l=0,r=0;
            while(dep[a]>dep[b])
            {
                L[++l]=a;
                a=pre[a];
            }
            while(dep[b]>dep[a])
            {
                R[++r]=b;
                b=pre[b];
            }
            while(a!=b)
            {
                L[++l]=a;
                R[++r]=b;
                a=pre[a];
                b=pre[b];
            }
            L[++l]=a;
            printf("%d\n",l+r);
            for(int i=1;i<=l;i++)
            printf("%d ",L[i]);
            for(int i=r;i>=1;i--)
            printf("%d ",R[i]);
            printf("\n");
        }
    }else
    {
        printf("NO\n");
        printf("%d\n",numofodd/2);
    }
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值