@codeforces - 1186F@ Vus the Cossack and a Graph


@description@

给定一个 n 点 m 边的图(n, m<=10^6),记第 i 个点的度数为 di。

现让你保留不超过 (n + m) / 2(向上取整)条边,并且要求新图中第 i 个点的度数 di' 满足 2di' ≥ di。

不难证明它一定有解。现你只需要输出任意一种方案。

input
第一行包含 n 和 m,表示点数与边数。
接下来 m 行每一行包含 2 个整数 ui, vi,描述一条边。
无重边、自环。

output
第一行首先输出保留的边的数量 k。
接下来 k 行每行两个整数 u, v,描述你所保留的边。

sample input
10 20
4 3
6 5
4 5
10 8
4 8
5 8
10 4
9 5
5 1
3 8
1 2
4 7
1 4
10 7
1 7
6 1
9 6
3 9
7 9
6 2
sample output
12
2 1
4 1
5 4
6 5
7 1
7 4
8 3
8 5
9 3
9 6
10 4
10 7

@solution@

好玄妙的题目啊。。。
但我想到的实现好像跟标算不大一样,不过用到的算法大致是一样的。

根据题目,偶数新度数最小为原度数的1/2,奇数新度数最小为(原度数+1)的1/2。
假如所有点的新度数都取最小值,则偶度点连接的边一半被删除,一半被保留;奇度点连接的某一条边保留,然后转为偶度点的情况。
这样将点的度数分奇偶讨论的过程,将某一个点连接的边分为相同大小的集合的操作,有没有让你联想到什么。

欧拉回路。即不重复、不遗漏经历所有边的路径。
欧拉回路可以将一个偶点连接的边分为“入边”和“出边”两类相同个数的边。
同时可以发现,以奇点开始奇点结束的欧拉路径,起点恰好多一条出边,终点恰好多一条入边。
这不和我们刚刚的讨论恰好相一致吗?

考虑欧拉回路,即没有奇数点的情况。
如果该欧拉回路长度为偶数,我们只需要隔一条边保留一条边即可,这样可以保证每一个点入边与出边保留恰好一条,且总边数减至一半。
否则如果长度为奇数,如 (1, 2), (2, 3), (3, 1),我们从第一条边开始隔一条边保留一条边,可以发现最后我们会同时保留第一条与最后一条,而这两条边是相邻的,故我们总边数变为原先的 1/2 再加 1 。
因为没有重边,长度为奇数必然包含两个以上的点,故最多会加 n/2 次。满足题设。

考虑欧拉路径,即起点和终点为奇数点的情况。
如果该欧拉回路长度为奇数,从起点开始隔一条边保留一条边即可。总边数变为原先的 1/2 加 1(因为奇数长度保留的要比删去的多一条)。
否则如果长度为偶数,如 (1, 2), (2, 3), (3, 4), (4, 5),我们从起点开始隔一条边保留一条边之后,再将与终点连接的边保留。总边数变为原先的 1/2 加 1。
最多会有 n 个奇数点,每两个奇数点之间产生一个路径,故最多会加 n/2 次。满足题设。

补充一小点:如何求解多条欧拉路径。你只需要将奇数点两两分组然后连接虚边,跑欧拉回路,然后两个虚边之间夹着的就是一条欧拉路径。如果没有虚边就是一条欧拉回路。

注意题目中给出的图可能不连通,你需要对每个连通块都进行操作。

@accepted code@

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 1000000;
const int MAXM = 5000000;
struct edge{
    int from, to;
    bool flag, tag;
    edge *nxt, *rev;
}edges[MAXM + 5], *adj[MAXN + 5], *ecnt = &edges[0];
int fa[MAXN + 5];
int find(int x) {
    return fa[x] = (fa[x] == x ? x : find(fa[x]));
}
void addedge(int u, int v, bool t) {
    edge *p = (++ecnt), *q = (++ecnt);
    p->from = u, p->to = v, p->nxt = adj[u], adj[u] = p;
    q->from = v, q->to = u, q->nxt = adj[v], adj[v] = q;
    p->flag = q->flag = false, p->tag = q->tag = t;
    p->rev = q, q->rev = p;
    if( find(u) != find(v) )
        fa[find(u)] = find(v);
}
int deg[MAXN + 5]; edge *e[MAXM + 5], *tmp[MAXM + 5];
int ans1[MAXM + 5], ans2[MAXM + 5];
int cnt, tot;
void dfs(int x) {
    for(edge *p=adj[x];p;p=adj[x]) {
        if( p->flag ) {
            adj[x] = adj[x]->nxt;
            continue;
        }
        p->flag = p->rev->flag = true;
        adj[x] = adj[x]->nxt; dfs(p->to); e[++cnt] = p;
    }
}
void print() {
    printf("%d\n", tot);
    for(int i=1;i<=tot;i++)
        printf("%d %d\n", ans1[i], ans2[i]);
}
int main() {
    int n, m; scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++)
        fa[i] = i;
    for(int i=1;i<=m;i++) {
        int u, v; scanf("%d%d", &u, &v);
        addedge(u, v, true); deg[u]++, deg[v]++;
    }
    int lst = 0, fir = 0;
    for(int i=1;i<=n;i++) {
        if( deg[i] & 1 ) {
            if( !lst ) lst = i;
            else addedge(lst, i, false), lst = 0;
        }
    }
    for(int i=1;i<=n;i++) {
        if( find(i) != i ) continue;
        cnt = lst = fir = 0; dfs(i);
        for(int j=1;j<=cnt;j++) {
            if( !e[j]->tag ) {
                if( lst ) {
                    int siz = 0;
                    for(int k=lst+1;k<=j-1;k++)
                        tmp[++siz] = e[k];
                    if( siz && siz % 2 == 0 ) tmp[siz+1] = tmp[siz], siz++;
                    for(int k=1;k<=siz;k+=2)
                        tot++, ans1[tot] = tmp[k]->from, ans2[tot] = tmp[k]->to;
                    if( siz && siz % 2 == 0 )
                        tot++, ans1[tot] = tmp[siz]->from, ans2[tot] = tmp[siz]->to;
                }
                else fir = j;
                lst = j;
            }
        }
        if( !lst ) {
            for(int j=1;j<=cnt;j+=2)
                tot++, ans1[tot] = e[j]->from, ans2[tot] = e[j]->to;
        }
        else {
            int siz = 0;
            for(int j=lst+1;j<=cnt;j++)
                tmp[++siz] = e[j];
            for(int j=1;j<=fir-1;j++)
                tmp[++siz] = e[j];
            if( siz && siz % 2 == 0 ) tmp[siz+1] = tmp[siz], siz++;
            for(int j=1;j<=siz;j+=2)
                tot++, ans1[tot] = tmp[j]->from, ans2[tot] = tmp[j]->to;
        }
    }
    print();
}

@details@

虽然题解里说的好像非常自然。
不过要是在做比赛的时候能想得到才有鬼好吧。

求欧拉路的时候通过打 tag 标记访问过的边会 TLE。需要类比网络流中的当前弧优化,将访问过的边直接删除。

猜你喜欢

转载自www.cnblogs.com/Tiw-Air-OAO/p/11114236.html