bzoj 3724: PA2014Final Krolestwo 欧拉回路+构造

5 篇文章 0 订阅
2 篇文章 0 订阅

题意

给定n个点m条边的无向连通图G(m为偶数),已知有k个点(k为偶数)度数为奇数。现在你要把它们两两配对,然后用k/2条连接这些点对的包含偶数条边的路径覆盖完G,需要保证每条边都恰好被覆盖一次(一条路径可以经过相同点)。无解输出NIE

1≤n,m≤250,000

分析

这道题很神
首先我们考虑如果没有路径长度为偶数应该怎么做,就是我们可以建一个新的点root,然后把奇数的点连上去,这样所有的度数都是偶数,只要找到欧拉回路,两个奇数点之间就是答案

现在多了一个路径长度为偶数的条件,我们把每个点拆成两个点i和i+n,把root和i连边
然后就像二分图一样交错连边,但是现在要保证度数都是偶数
我们可以这样保证:首先把原图的任意一个生成树给提出来,剩下的边随便连,而生成树上的边可以调度数,我们在生成树上从1开始搜索

假设有一条树边(x,fa),那么如果cnt[x] 是奇数,那么就在新图上连(x,fa+n)
否则的话就连(x+n,fa)

这样的话只有1这个点的度数可能会有问题,但是我们想想,首先1和n+1的度数是奇偶性相同的,因为其它的点的度数都是偶数,再其次,题目有边数是偶数的条件,我们的建边是按二分图来建的,根据二分图的性质,1和n+1肯定是偶数,因为从一边开始走,所有点度数是偶数,肯定能回到这一边的

之后的话我们在新图跑一遍欧拉回路就可以了,又考虑刚才的正确性
一些边建在(x,y+n)或(x+n,y),其实只要考虑有一条欧拉同路,那些边建在哪里只是代表不同的方案而已

代码

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 500010;
inline int read()
{
  char ch=getchar(); int p=0; int f=1;
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

struct node
{
  int x,y,next,id;
}edge[N<<1]; int len,first[N]; int cnt[N];
void ins(int x,int y,int id)
{
  len++; edge[len].x=x; edge[len].y=y; edge[len].id=id; edge[len].next=first[x]; first[x]=len;
  len++; edge[len].x=y; edge[len].y=x; edge[len].id=id; edge[len].next=first[y]; first[y]=len;
  cnt[x]++; cnt[y]++;
}

int fa[N];
int Find(int x){return (fa[x]==x)?fa[x]:fa[x]=Find(fa[x]);}

vector<int> v[N],vi[N]; int n,m;

void build(int x,int f,int id)
{
  for(int i=0;i<v[x].size();i++) if(v[x][i] != f) build(v[x][i],x,vi[x][i]);
  if(f)
  {
    if(cnt[x] & 1) ins(x,(f==0) ? f : f+n,id);
    else ins(x+n,f,id);
  }
}

int s[N],top=0; bool vis[N];
void dfs(int x)
{
  while(1)
  {
    int k = first[x]; if(k==-1) break; first[x] = edge[k].next;
    if(vis[k>>1]) continue; vis[k>>1] = 1;
    dfs(edge[k].y);
    s[++top] = edge[k].id;
  }
}

int d[N];

int main()
{

  n = read(); m = read();

  len = 1; memset(first,-1,sizeof(first));

  for(int i=1;i<=n;i++) fa[i]=i;
  for(int i=1;i<=m;i++)
  {
    int x=read(); int y=read(); d[x]++; d[y]++;
    if(Find(x) == Find(y)) ins(x,y+n,i);
    else
    {
      fa[Find(x)] = Find(y);
      v[x].pb(y); v[y].pb(x);
      vi[x].pb(i); vi[y].pb(i);
    }
  }

  for(int i=1;i<=n;i++) if(d[i] & 1) ins(0,i,m+i);

  build(1,0,0);
  dfs(0);

  while(top > 0)
  {
    int x=s[top--]-m;
    int j=top; while(s[j] <= m && j>1) j--;
    int y=s[j]-m;
    printf("%d %d %d\n",x,y,top-j);
    for(int i=top;i>j;i--) printf("%d%c",s[i]," \n"[i==j+1]);
    top = j-1;
  }

  return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值