@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。需要类比网络流中的当前弧优化,将访问过的边直接删除。