2018 Multi-University Training Contest 2-C-Cover

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xbb224007/article/details/81229117

题目链接:HDU6311-Cover

(一)题面:

Problem Description

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

(二)题意:

题意比较简单,给出一个无向图(不一定连通),问最少要几笔画才能把整个图的所有边画一遍(一笔画不能画重复的边),并输出路径。

 

(三)题解:

不同连通块相互独立,结果互不影响,最后结果为各个连通块的结果之和。故下面只分析某个连通图。

设图中度数为奇数的顶点的个数为d:

若d==0,则图中存在欧拉回路,从任意一点出发均可以回到起点,一笔画即可完成。

若d==2,则图中存在欧拉路径,从一个度数为奇数的顶点出发,可以一笔画画完所有边且到达另一个度数为奇数的顶点。

若d>2,则图不能一笔画完成,需要的笔画数最少为d/2。可以这么考虑,一条欧拉路径可以从一个度数为奇数的顶点出发达到另外一个度数为奇数的顶点,故一笔画可以减少两个奇数度数的顶点,d个奇数度数顶点的图需要d/2笔画完成。

接下来就是考虑路径输出,这才是本题的难点。

我们将奇数度数的顶点两两匹配(加入“虚边”连接),那么可以得到一个欧拉图,然后跑一边欧拉回路可以得到一条路径,再将刚刚加入的边去掉,就可以得到多条路径,也就是最终多笔画的结果。

然而我按照上述匹配方法做却WA生活不能自理,最后发现如果将所有的奇数度数的顶点匹配,再去跑欧拉回路可能会出问题(基于我的写法)。比如面一个简单的例子:

图中的虚线为加入的边,得到欧拉图后,如果我从2点出发得到回路为2->3->4->1,由于在途中碰到了虚边,那么我就会将4->1这条边断开(我是在搜所路径的过程中就存路径,碰到虚边存入新的一笔画),最终就会输出两条路径。

其实我这个的解决方案也比较简单,在匹配顶点的时候只去匹配d-2个顶点,即留下两个奇数度数的顶点不匹配,然后其中从一个出发,便可以得到一条到达另一个顶点的欧拉路径,然后将加入的边去掉,仍然可以得到最终结果,并且不会出现上述情况(将可以一笔画的两条路径断开)。

总的复杂度O(n+m)。

 

(四)代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=2e5+10;
struct Edge{
    int from,to,vis,id,next,dir;
    Edge(){from=to=vis=next=dir=id=0;}
    void init(){from=to=vis=next=dir=id=0;}
}edges[maxn<<1];
int edgehead[maxn],e=2,deg[maxn];
void Addedge(int u,int v,int dir,int id){
    edges[e].to=v;
    edges[e].vis=0;
    edges[e].id=id;
    edges[e].dir=dir;
    edges[e].next=edgehead[u]; //从u出发的第一条边的编号
    edgehead[u]=e++;
}
int vi[maxn],road=0;
vector<int>set;
vector<int>Road[maxn];
void Get_set(int u){
    vi[u]=1;
    if(deg[u]&1)set.push_back(u);
    for(int i=edgehead[u];i;i=edges[i].next){
        if(!vi[edges[i].to])
            Get_set(edges[i].to);
    }
}
void Get_Road(int u){
    for(int i=edgehead[u];i;i=edges[i].next){
        if(!edges[i].vis){
            edges[i].vis=edges[i^1].vis=1;
            Get_Road(edges[i].to);
            if(!edges[i].id)road++;
            else Road[road].push_back(i^1);
        }
    }
}
void init(int n,int m){
    road=0;e=2;
    memset(vi,0,sizeof vi);
    memset(deg,0,sizeof deg);
    memset(edgehead,0,sizeof edgehead);
    for(int i=0;i<=n;i++)Road[i].clear();
    for(int i=0;i<m*3;i++)edges[i].init();
}
int main(){
//    freopen("1003.in","r",stdin);
//    freopen("My1003.out","w",stdout);
    int n,m,u,v;
    while(~scanf("%d%d",&n,&m)){
        init(n,m);
        for(int i=1;i<=m;i++){
            scanf("%d%d",&u,&v);
            Addedge(u,v, 1,i);
            Addedge(v,u,-1,i);
            deg[u]++;deg[v]++;
        }
        for(int i=1;i<=n;i++){
            if(deg[i]&&!vi[i]){
                Get_set(i);
                for(int j=2;j<set.size();j+=2){
                    Addedge(set[j],set[j+1],0,0);
                    Addedge(set[j+1],set[j],0,0);
                }
                road++;
                if(set.size())Get_Road(set[0]);
                else Get_Road(i);
                set.clear();
            }
        }
        printf("%d\n",road);
        for(int i=1;i<=road;i++){
            printf("%d",(int)Road[i].size());
            for(int j=0;j<Road[i].size();j++){
                printf(" %d",edges[Road[i][j]].id*edges[Road[i][j]].dir);
            }
            printf("\n");
        }
    }
    return 0;
}

 

(五)总结:

复习了一遍求欧拉路,以及图的一些性质。

学习了用邻接表存图(基于数组),虽然看别人用得不少,但是以前自己基本上都是用邻接矩阵或者vector进行储存,而本题邻接矩阵存不了,而vector也不太适用(有点慢)。

邻接表删边的小技巧:将互为反向边的两条边存到2*k和2*k+1的位置,删边i的时候就只要把i^1这条边删掉就可以了,比较方便。

猜你喜欢

转载自blog.csdn.net/xbb224007/article/details/81229117