JZOJ 5404. 【NOIP2017提高A组模拟10.10】Graph

题目

给定一张n个点m条边的无向图,每条边连接两个顶点,保证无重边自环,不保证连通
你想在这张图上进行若干次旅游,每次旅游可以任选一个点x作为起点,再走到一个与x 直接有边相连的点y,再走到一个与y 直接有边相连的点z 并结束本次旅游
作为一个旅游爱好者,你不希望经过任意一条边超过一次,注意一条边不能即正向走一次又反向走一次,注意点可以经过多次,在满足此条件下,你希望进行尽可能多次的旅游,请计算出最多能进行的旅游次数并输出任意一种方案。

题解

我们很容易就发现,一个点数为k的连通块,它的最大旅游次数一定为 k12 ,因为我们只两两匹配边,点可以重复选。
问题难在怎么构造。
从根节点往叶节点构造是很难的, 所以我们从下往上构造。
因为先匹配深度大,再匹配深度小的点,就能保证深度大的一定能被匹配,深度小的以后可以再匹配。
反之如果先匹配深度小,再匹配深度大的点,那么深度小的不知道它所连的下面的情况处理好没有。举例:如图,按照先匹配深度小的法则我一定会匹配1,2号边,但是3,4号边没得被匹配。
所以这样得不到理论上的最大值。
这里写图片描述
具体做法:先将儿子的边两两匹配,匹配剩的就看看能否和它的父亲匹配。
这道题的心得:
①遇到一眼就能看出大概的解题方向(或结论)的题目,多在草稿纸上画几个图。
②最重要的一点,这是一道构造题,必须要使局部的情况已经讨论完,才能进入下一级。就比如说从上往下匹配为什么不行。因为下面的状态情况还没有讨论完,且下面完全可能影响上面。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    int to,next;
};note edge[N*4];
struct note1{
    int x,y,z;
};note1 ans[N*2];
int i,j,k,l,n,m,now,gx,gy,u,v;
int t,w,cnt;
int e[N],tot,T;
int fa[N],tar[N];
int head[N];
bool bz[N*4],vis[N];
void lb(int x,int y){
    edge[++tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
void work(int S){
    int t=0,T=1,i;
    fa[S]=-1;
    tar[1]=S;
    while(t<T){
        now=tar[++t];
        for(i=head[now];i;i=edge[i].next)
            if(!fa[edge[i].to]){
                fa[edge[i].to]=now;
                tar[++T]=edge[i].to;
            }
    }
    while(T--){
        now=tar[T+1];
        e[0]=0;
        for(i=head[now];i;i=edge[i].next)
            if(!bz[i] && edge[i].to!=fa[now])e[++e[0]]=i;
        for(i=head[now];i;i=edge[i].next)
            if(!bz[i] && edge[i].to==fa[now])e[++e[0]]=i;
        for(i=1;i+1<=e[0];i+=2){
            bz[e[i]]=bz[e[i]^1]=1;
            bz[e[i+1]]=bz[e[i+1]^1]=1;
            ans[++cnt].x=edge[e[i]].to;
            ans[cnt].y=now;
            ans[cnt].z=edge[e[i+1]].to;
        }
    }
}
int main(){
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    scanf("%d%d",&n,&m);
    tot=1;
    fo(i,1,m){
        scanf("%d%d",&u,&v);
        lb(u,v);lb(v,u);
    }
    fo(i,1,n)if(!fa[i])work(i);
    printf("%d\n",cnt);
    fo(i,1,cnt)printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].z);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值