【HDU - 6311】Cover

@Cover@


@题目描述 - English@

The Wall has down and the King in the north has to send his soldiers to sentinel.
The North can be regard as a undirected graph (not necessary to be connected), one soldier can cover one path. Today there’s no so many people still breathing in the north, so the King wants to minimize the number of soldiers he sent to cover each edge exactly once. As a master of his, you should tell him how to arrange soldiers.

Input
There might be multiple test cases, no more than 20. You need to read till the end of input.
In the first line, two integers n and m, representing the number of nodes and edges in the graph.
In the following m lines, each contain two integers, representing two ends of an edge.
There are no parallel edges or self loops.
1≤n,m≤100000

Output
For each test case, the first line contains number of needed routes, p.
For the following p lines, an integer x in the beginning, followed by x integers, representing the list of used edges. Every integer should be a positive or negative integer. Its absolute value represents the number of chosen edge (1~n). If it’s positive, it shows that this edge should be passed as the direction as the input, otherwise this edge should be passed in the direction different from the input. Edges should be in correct order.

Sample Input
3 3
1 2
1 3
2 3
Sample Output
1
3 1 3 -2

@大致题意@

给定一个无向无自环无重边的图,求最少使用多少条路径才能将所有边覆盖。路径可以包含重复的点但不能包含重复的边。输出方案,详细输出格式见英文题目。

@分析@

假设只用一条路径就可以覆盖所有边,那么这个图一定是一个欧拉回路或欧拉路径,即不包含奇数度的点或只包含两个奇数度的点的图。欧拉路径有着这样一个推广的结论:假设一个联通块内包含n个奇数度的点(n≠0),那么至少需要n/2条路径才可以将所有边覆盖。
所以我们这样统计答案:对于一个联通块,假设它里面不含奇点,并且它的大小≠1(注意必须要这个特判),则答案+1;如果它包含奇点,统计它包含有多少个奇点,答案+奇点数/2。

对于简单的欧拉回路与欧拉路径,我们可以很简单的输出方案。但是对于推广出来的结论,我们应该如何输出方案?这其实是一个很经典的欧拉路径扩展出来的问题,很多有关欧拉路径的难题大都是由这个问题变形而来。

我们这样构造:对于某一个联通块,把块内的奇点两两一组,将每一组内的两个点用一条虚边连接。这样就会连接n/2条虚边,并且连接虚边后的新图并不存在奇点。在新图上跑欧拉回路,就可以得到一个边序列 A。注意到这个边序列其实是圆环形的,即 A[1] 与 A[N] 是可以连在一起的【因为它是欧拉回路,从任何一条边出发都是等价的】。因为有n/2条虚边,就会产生n/2个不含虚边的连续序列,且每个序列之间都间隔一个虚边。这时我们发现,这n/2个连续边序列恰好就是我们所需要的方案!同时我们发现,这样子同时也就证明了推广定理的正确性。
实现上,为了避免圆环形序列,我们总让边序列的第一个元素为虚边。

于是本题到这里就解决得差不多了。

@代码@

【当初我做Hack It做到自闭时,队友就是做这道题做到自闭……】
【唉……见的题目太少了QAQ……果然我们只是辣鸡啊QAQ……】
代码的实现可能有些细节上面没有提到,不过大体思路还是按照上面来的,可以参考一下代码细节以更好地理解。
如果有什么疑问就可以评论下面,我会尽力解疑的~

#include<cstdio>
#include<vector>
using namespace std;
const int MAXN = 100000;
struct edge{
    int to, num; bool vis;
    edge *nxt, *rev;
}edges[4*MAXN + 5], *ecnt, *adj[MAXN + 5];
int n, m, deg[MAXN + 5], a[MAXN + 5];
int hve[MAXN + 5], siz[MAXN + 5], fa[MAXN + 5];
vector<int>arr, ans;
void addedge(int u, int v, int w) {
    edge *p = (++ecnt);
    p->num = w, p->to = v, p->vis = false;
    p->nxt = adj[u], adj[u] = p;
    edge *q = (++ecnt);
    q->num = -w, q->to = u, q->vis = false;
    q->nxt = adj[v], adj[v] = q;
    q->rev = p, p->rev = q;
}
void init() {
    for(int i=1;i<=n;i++) {
        a[i] = -1;
        deg[i] = 0;
        siz[i] = 1;
        fa[i] = i;
        adj[i] = NULL;
        hve[i] = 0;
    }
    ecnt = &edges[0];
}
int Find(int x) {
    if( fa[x] == x ) return x;
    else return fa[x] = Find(fa[x]);
}
void Union(int x, int y) {
    int fx = Find(x), fy = Find(y);
    if( fx != fy )
        fa[fx] = fy, siz[fy] += siz[fx];
}
void dfs(int x) {
    for(edge *p=adj[x];p!=NULL;p=p->nxt) {
        if( !p->vis ) {
            p->vis = p->rev->vis = true;
            dfs(p->to);
            arr.push_back(p->num);
        }
    }
}
void solve() {
    init();
    for(int i=1;i<=m;i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v, i);
        Union(u, v);
        deg[u]++, deg[v]++;
    }
    for(int i=1;i<=n;i++) {
        if( deg[i] & 1 ) {
            hve[Find(i)]++;
            if( a[Find(i)] == -1 )
                a[Find(i)] = i;
            else {
                addedge(a[Find(i)], i, 0);//令虚边编号为0,实边编号为非0数 
                a[Find(i)] = -1;
            }
        }
    }
    int tot = 0;
    for(int i=1;i<=n;i++) {
        if( fa[i] != i ) continue;
        if( !hve[i] ) {
            if( siz[i] != 1 )
                tot++;
        }
        else tot += hve[i]/2;
    }
    printf("%d\n", tot);
    for(int i=1;i<=n;i++) {
        if( hve[Find(i)] ) {
            if( deg[i] & 1 ) {
                dfs(i);
//因为指针版邻接表的特性,后加入的边就会在最前面,所以就一定保证了第一条访问到的边一定是虚边 
                if( arr.empty() ) continue;
                int p = arr.size()-2;
                while( p >= 0 ) {
                    if( arr[p] == 0 ) {
                        printf("%d", int(ans.size()));
                        for(int i=0;i<ans.size();i++)
                            printf(" %d", ans[i]);
                        puts("");
                        ans.clear();
                        p--;
                    }
                    ans.push_back(arr[p]);
                    p--;
                }
                printf("%d", int(ans.size()));
                for(int i=0;i<ans.size();i++)
                    printf(" %d", ans[i]);
                puts("");
                ans.clear(); arr.clear();
            }
        }
        else {
            dfs(i);
            if( arr.empty() ) continue;
            printf("%d", int(arr.size()));
            for(int i=arr.size()-1;i>=0;i--)
                printf(" %d", arr[i]);
            puts("");
            arr.clear();
        }
    }
}
int main() {
    while( scanf("%d%d", &n, &m) == 2 )
        solve();
}

@ENDING@

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值