[日常训练] Graph

Time limit: 2s Memory limit: 512MB

Description

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

Input Format

  • 1行两个正整数 n m,表示全图的点数与边数下接 m 行,每行两个数字u v 表示一条边

Output Format

  • 1行一个整数 cnt 表示答案下接 cnt 行,每行三个数字 x,y,z ,表示一次旅游的路线如有多种旅行方案,任意输出一种即可

Sample Input

  • 4 5 1 2 3 2 2 4 3 4 4 1

Sample Output

  • 2 4 1 2 4 3 2

Constraints

  • 对于前 20% 的数据, n10,m20
  • 对于令 20% 的数据, m=n1 ,并且图连通
  • 对于令 10% 的数据,每个点的度数不超过 2
  • 对于 100% 的数据,n100000,m200000

Solution 构造 + DFS

  • 显然,题目是要我们尽可能多地找出像下面这样的“东西”把整张图覆盖完
  • 考虑到这里有一个公共点 y ,我们不妨把这两条边看作是分配到点y
  • 那么显然,当一个点分配的边数为偶数时,我们可以通过两两任意组合构造出一种匹配完所有边的方案,为奇数时则会剩下一条边
  • 进一步的,我们可以通过这一性质构造出一种最优方案
  • 首先说一下结论:对于一个边数为 m 连通快,最优方案必然有m2条旅行路线
  • 我们先对这张图构造出一棵 DFS 树,显然图中有非树边和树边两种边
  • 然后按照 DFS 的顺序处理分配方案,也就是处理到某一节点 x 时,它子树中所有点的分配方案已经处理完了
  • 那么此时对于这个点x:它所能选择的边有它的父亲边,它未被选择过的儿子边以及连向它的未被选择过的非树边
  • 我们把后两者先分配到点 x ,那么我们就总能通过调整父亲边是否分配给点x来使得点 x <script type="math/tex" id="MathJax-Element-2095">x</script>分配到的边数固定为偶数
  • 于是最后只可能在根节点处剩下一条边,并且是在总边数为奇数的情况下,则我们上面所说的结论成立,也因此构造出了一组方案

Code

#include <iostream>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm> 

using namespace std;

const int S = 1 << 20;
char frd[S], *hed = frd + S;
const char *tal = hed;

inline char nxtChar()
{
    if (hed == tal)
        fread(frd, 1, S, stdin), hed = frd;
    return *hed++;
}

inline int get()
{
    char ch; int res = 0;
    while (!isdigit(ch = nxtChar()));
    res = ch ^ 48;
    while (isdigit(ch = nxtChar()))
        res = res * 10 + ch - 48;
    return res;
}

inline void put(int x)
{
    if (x > 9) put(x / 10);
    putchar(x % 10 + 48); 
}

const int N = 1e5 + 5, M = N << 2;
int n, m, len, Ans;
bool vis[N], stp[M];

struct Edge 
{
    int num, to; Edge *nxt;
};

Edge p[M], *T = p, *lst[N];
Edge q[M], *Q = q, *rst[N];

inline void LinkEdge(int x, int y, int z)
{
    (++T)->nxt = lst[x]; lst[x] = T; T->to = y; T->num = z;
    (++T)->nxt = lst[y]; lst[y] = T; T->to = x; T->num = z;
}

inline void RinkEdge(int x, int y)
{
    (++Q)->nxt = rst[x]; rst[x] = Q; Q->to = y; ++len;
}

inline void Dfs(int x, int fa, int I)
{
    int cnt = 0, y; 
    for (Edge *e = lst[x]; e; e = e->nxt)
    {
        if (vis[y = e->to]) 
        {
            if (y == fa || stp[e->num]) continue;
            ++cnt;
            stp[e->num] = true;
            RinkEdge(x, y);
            continue;
        }
        vis[y] = true;
        Dfs(y, x, e->num);
    }   

    for (Edge *e = lst[x]; e; e = e->nxt)
    if (vis[y = e->to] && !stp[e->num])
    {
        if (y == fa) continue;
        ++cnt;
        stp[e->num] = true;
        RinkEdge(x, y);
    }

    if ((cnt & 1) && fa) 
        stp[I] = true, RinkEdge(x, fa); 
}
int main()
{
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);

    n = get(); m = get(); int x, y;
    for (int i = 1; i <= m; ++i)
    {
        x = get(); y = get();
        LinkEdge(x, y, i); 
    }

    for (int i = 1; i <= n; ++i)
        if (!vis[i]) 
        {
            len = 0; vis[i] = true;
            Dfs(i, 0, 0);
            Ans += len >> 1;
        }

    put(Ans), putchar('\n');
    for (int i = 1; i <= n; ++i)
        for (Edge *e = rst[i]; e && e->nxt; e = e->nxt->nxt)
            {
                put(e->to), putchar(' ');
                put(i), putchar(' ');
                put(e->nxt->to), putchar('\n');
            }

    fclose(stdin); fclose(stdout);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值