图论--欧拉路,欧拉回路(小结)

在题目中在慢慢细说概念
1.HDU - 3018 Ant Trip
题目大意:又N个村庄,M条道路。问须要走几次才干将全部的路遍历

解题思路:这题问的是有关欧拉路的判定
欧拉路就是每条边仅仅能走一次,且要遍历全部的边,简单的说就是一笔画(图连通)
这道题是无向图的欧拉路。无向图的欧拉路的判定:全部点的度数都是偶数度,或者仅仅有两个点的度是奇数度,且图要是连通图

知道欧拉路是什么后,这题就比較好做了,第一件事就是找到有几个连通块,然后再推断一下每一个连通块须要几笔才干完毕就好了

#include 
#include 
#include 
#include 
using namespace std;
#define N 100010

int node[N];
int n, m;
int p[N];
int vis[N], vis2[N];
int num[N];
map<int,int> M;

int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}

void init() {

    M.clear();
    memset(node, 0, sizeof(node));
    memset(vis, 0, sizeof(vis));
    memset(num, 0, sizeof(num));
    memset(vis2, 0, sizeof(vis2));
    int x, y, px, py;

    for (int i = 0; i <= n; i++)
        p[i] = i;

    for (int i = 0; i < m; i++) {
        scanf("%d%d", &x, &y);
        px = find(x);
        py = find(y);
        if (px != py) {
            p[px] = py;
        }
        node[x]++;
        node[y]++;
        vis[x] = 1;
        vis[y] = 1;
    }
}

void solve() {
    int cnt = 0;
    for (int i = 0; i <= n; i++) {
        if (vis[i]) {
            int x = find(i);
            if(!vis2[x]) {
                vis2[x] = 1;
                M[x] = cnt++;
            }
        }
    }

    for (int i = 0; i <= n; i++) {
        if (vis[i] && node[i] % 2) {
            int x = find(i);
            num[M[x]]++;
        }
    }

    int ans = cnt;
    for(int i = 0; i < cnt; i++) 
        if(num[i])
            ans += ((num[i] - 1) / 2);
    printf("%d\n", ans);
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        init();
        solve();
    }
    return 0;
}

2.POJ - 1386 Play on Words

题目大意:问全部的单词是否能首尾相连

解题思路:这题也是求欧拉路的,仅仅只是这题是有向的
有向图的欧拉路判定:首先,要是连通的。接着就分成两种了,一种是全部点的入度等于出度,还有一种是仅仅有两个点的入度不等于出度。且当中一个点的入度比出度大一,还有一个点的出度比入度大一

#include 
#include 
#include 
#define N 30
#define M 1010

int in[N], out[N], n;
char str[M];
int p[N], vis[N];
int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}

void init() {
    scanf("%d", &n);

    for (int i = 0; i < N; i++)
        vis[i] = out[i] = in[i] = 0;

    for (int i = 0; i < N; i++)
        p[i] = i;

    int len, x, y, px, py;
    for (int i = 0; i < n; i++) {
        scanf("%s", str);
        len = strlen(str);

        x = str[0] - 'a';
        y = str[len - 1] - 'a'; 

        in[x]++;
        out[y]++;

        vis[x] = 1;
        vis[y] = 1;
        px = find(x);
        py = find(y);

        if(px != py) {
            p[px] = py;
        }
    }
}

bool connect() {
    int start = 0;

    for (; !vis[start]; start++);
    for (int i = start + 1; i < N; i++) {
        if (vis[i] && find(i) != find(start)) {
            return false;
        }
    }
    return true;
}

void solve() {

    int cnt = 0; 
    bool mark = false;
    bool flag1 = false, flag2 = false;
    for (int i = 0; i < N; i++) {
        if (in[i] - out[i] != 0) {
            cnt++;
            if (cnt >= 3)
                break;

            if (abs(in[i] - out[i]) != 1) {
                mark = true;
                break;
            }

            if (in[i] - out[i] == -1)
                flag1 = true;
            if (in[i] - out[i] == 1)
                flag2 = true;
        }
    }

    if(mark || cnt >= 3) {
        printf("The door cannot be opened.\n");
        return ;
    }

    bool flag3 = connect();
    if (cnt == 0 && flag3) {
        printf("Ordering is possible.\n");
    }
    else if (cnt == 2 && flag1 && flag2 && flag3) {
        printf("Ordering is possible.\n");
    }
    else {
        printf("The door cannot be opened.\n");
    }

}

int main() {
    int test;
    scanf("%d", &test);
    while (test--) {
        init();
        solve();
    }
    return 0;
}

下面的题目都要用到Fluery算法
3.POJ - 1041 John’s trip

题目大意:有n个城市,m条路,问是否能走遍全部的路,最后又回到起点

解题思路:问是否能形成欧拉回路,并要求输出路径的题目
首先是欧拉回路的判定,对无向图来说,全部的点的度数都为偶数就是欧拉回路了。对有向图来说。是全部的点的入度等于出度

事实上上面的判定我少加了一个条件,那就是图连通
这题是无向图的。所以判定起来比較简单,关键是怎样输出路径了
事实上这题是非常坑的,所要求的输出最小字典序,事实上并不须要,仅仅须要常规的输出就能够了
事实上这一题也不须要了解fluery算法,仅仅须要dfs输出就能够了。dfs能够保证路线连通,在设置一个标记就能够输出全部路径了

#include 
#include 
#include 
#include 
#include 
using namespace std;
#define N 2000
#define M 50

struct Edgs{
    int junc, street;
    Edgs(int s = 0, int j = 0) : junc(j), street(s) {}
};

int degrees[M];
vector E[N];
stack<int> stk;
bool vis[N];
int Max, x, y, z;

void Euler(int u) {
    for (int i = 0; i < E[u].size(); i++) {
        if (!vis[E[u][i].street]) {
            vis[E[u][i].street] = true;
            Euler(E[u][i].junc);
            stk.push(E[u][i].street);
        }
    }
}


void solve() {
    for (int i = 0; i < M; i++) {
        if (degrees[i] & 1) {
            printf("Round trip does not exist.\n");
            return ;
        }
    }

    Euler(Max);
    while (!stk.empty()) {
        printf("%d ", stk.top());
        stk.pop();
    }
    printf("\n");
}

void init() {
    scanf("%d", &z);

    Max = max(x, y);
    memset(degrees, 0, sizeof(degrees));
    memset(vis, 0, sizeof(vis));
    for(int i = 0; i < N; i++)
        E[i].clear();

    E[x].push_back(Edgs(z,y));
    E[y].push_back(Edgs(z,x));
    degrees[x]++;
    degrees[y]++;

    while(scanf("%d%d", &x, &y) && x + y) {
        scanf("%d", &z);
        E[x].push_back(Edgs(z,y));
        E[y].push_back(Edgs(z,x));
        degrees[x]++;
        degrees[y]++;
    }
}

int main() {
    while (scanf("%d%d", &x, &y) != EOF && x + y) {
        init();
        solve();
    }
    return 0;
}

4.POJ - 2230 Watchcow

题目大意:有N个点,M条边,要求每条边都走两次。且最后回到原点,输出走的点

解题思路:每条边走两次,不就相当于把一条边变成两条边吗,那就变成两条边再去遍历就能够了

#include 
#include 
#include 
using namespace std;
#define N 10010

vector<int> Edgs[N];
int n, m;
int vis[N];

void init() {
    for (int i = 1; i <= n; i++)
        Edgs[i].clear();

    int x, y;
    for (int i = 0; i < m; i++) {
        scanf("%d%d", &x, &y);
        Edgs[x].push_back(y);
        Edgs[y].push_back(x);
    }
}

void Euler(int u) {

    int tmp;
    for (int i = 0; i < Edgs[u].size(); i++) {
        tmp = Edgs[u][i];
        if(!tmp)
            continue;
        Edgs[u][i] = 0;
        Euler(tmp);
    }
    printf("%d\n", u);
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF ) {
        init();
        Euler(1);
    }
    return 0;
}

5.POJ - 2404 Jogging Trails

插播一题欧拉回路+floyd+状态压缩的题目

题目大意:有N个点,M条路。每条路都有对应的权值。如今有一个人想把全部的路都跑遍,最后又回到原点。问最小权值是多少

解题思路:跑欧拉回路的话,肯定能使权值到达最小。
所以问题就变成了,通过增边将M条路构造成欧拉回路
问题进一步变成了怎样增边才干使权值达到最低,这就须要用到状态压缩dp了

#include 
#include 
#include 
#include 
using namespace std;
#define N 20
#define INF 0x3f3f3f3f
#define S (1<<15)

int n, m, ans;
int dis[N][N], degrees[N], dp[S];

void floyd() {
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                if(dis[i][k] != INF && dis[k][j] != INF && dis[i][j] > dis[i][k] + dis[k][j])
                    dis[i][j] = dis[i][k] + dis[k][j];
}

int dfs(int state) {
    if (state == 0) { 
        return 0;
    }
    if (dp[state] != -1) {
        return dp[state];
    }

    dp[state] = INF;
    for (int i = 0; i < n; i++) {
        if (state & (1 << i)) {
            for (int j = 0; j < n; j++) {
                if ((state & (1 << j)) && i != j) {
                    dp[state] = min(dp[state], dfs(state ^ (1 << i) ^ (1 << j)) + dis[i][j]);
                } 
            }
        }
    }
    return dp[state];
}

void solve() {

    int state = 0;
    for (int i = 0; i < n; i++) {
        if (degrees[i] % 2) {
            state |= (1 << i);
        }
    }
    memset(dp, -1,sizeof(dp));
    ans += dfs(state);
    printf("%d\n", ans);
}

void init() {

    ans = 0;
    memset(dis, 0x3f, sizeof(dis));
    for (int i = 0; i < N; i++) 
        degrees[i] = 0;
    for (int i = 0; i < n; i++)
        dis[i][i] = 0;

    int x, y, z;
    scanf("%d", &m);
    for (int i = 0; i < m; i++) {
        scanf("%d%d%d",&x, &y, &z);
        dis[x - 1][y - 1] = dis[y - 1][x - 1] = min(dis[x - 1][y - 1], z);
        ans += z;
        degrees[x - 1]++;
        degrees[y - 1]++;
    }

    floyd();
}

int main() {
    while (scanf("%d", &n) != EOF && n) {
        init();
        solve();
    }
    return 0;
}

6.POJ - 2337 Catenyms

题目大意:这题就比較有难度了。问是否能将字符串头尾相连,假设能够的话,输出字典序最小的字符串

解题思路:參考了学长的。有向欧拉路的推断就不说了,关键是怎样输出字典需最小的
首先。先给全部的字符串排序
依照fleury算法。输出时根据栈里的内容输出,而栈是从顶究竟输出的。也就是说。压栈的时候。先将字典序大的压栈就可以

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define N 30
#define M 1010

vector<int> v[N];
int vis[M], p[N], in[N], out[N];
char word[M][N];
int n;
stack<int> stk;

int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}
void output() {
    while (!stk.empty()) {
        printf("%s", word[stk.top()]);
        stk.pop();
        if (!stk.empty()) {
            printf(".");
        }
    }
}

int cmp (const void *a, const void *b) {
    return strcmp((char *)a, (char *)b);
}

void push (int u, int v) {
    for (int i = n - 1; i >= 0; i--) {
        if(!vis[i] && word[i][0] - 'a' == u && word[i][strlen(word[i]) - 1] - 'a' == v)  {
            stk.push(i);
            vis[i] = 1;
            return ;
        }
    }
}
void dfs(int u) {
    while (!v[u].empty()) {
        int tmp = *(v[u].begin());
        v[u].erase(v[u].begin());
        dfs(tmp);
        push(u, tmp);
    }
}

void solve() {
    int root;
    for (int i = 0; i < N; i++) {
        if (vis[i]) {
            root = find(i);
            break;
        }
    }

    for (int i = 0; i < N; i++) {
        if (vis[i] && find(i) != root) {
            printf("***");
            return ;
        }
    }

    int start = 0, cnt;
    for (int i = 0; i < N; i++) {
        if (abs(in[i] - out[i]) > 1) {
            printf("***");
            return ;
        }
        if (abs(in[i] - out[i]) == 1) {
            cnt++;
            if(in[i] - out[i] == 1)
                start = i;
        }
    }

    if(cnt > 2) {
        printf("***");
        return ;
    }
    if(!cnt)
        start = word[0][0] - 'a';
    memset(vis, 0, sizeof(vis));
    dfs(start);
    output();
}

void init() {
    scanf("%d", &n);

    for (int i = 0; i < n; i++)
        scanf("%s", word[i]);

    qsort(word, n, sizeof(word[0]), cmp);
    memset(vis, 0, sizeof(vis));

    for (int i = 0; i < N; i++) {
        v[i].clear();
        in[i] = out[i] = 0;
        p[i] = i;
    }

    int x, y;
    for (int i = 0; i < n; i++) {
        x = word[i][0] - 'a';
        y = word[i][strlen(word[i]) - 1] - 'a';
        v[x].push_back(y);
        vis[x] = vis[y] = 1;
        in[x]++;
        out[y]++;
        p[find(x)] = find(y);
    }
}

int main() {
    int test;
    scanf("%d", &test);
    while(test--) {
        init();
        solve();
        printf("\n");
    }
    return 0;
}

接下来的题目,算是欧拉路的应用吧

7.UVA - 10040 Ouroboros Snake

题目大意:有个轮子上面有2 * n个数字,有n个数字是0,n个数字是1。这个轮子非常奇妙,在纸上滚动时。会将轮子上面的0。1印在纸张上。细致看纸张上面的数字,从头開始。分别从第1个到第2^n个截取出长度为n的二进制数,会发现这些二进制数包括了0—-(1 << n)-1的全部数
如今要求你按规则输出第k个开头,长度为n的二进制数

解题思路:由于包括了全部0–(1 << n)-1的数。这些长度为n的二进制数都是0-1构成的,且全部的出度等于入度,所以这就形成了一个欧拉回路了
因此在构造这个轮子在纸张上打印的字时能够依照输出欧拉回路的方法构造。

具体的能够看这篇文章。里面有对代码的具体讲解代码具体解释

#include 
#include 
#include 
using namespace std;
#define N 16
#define S (1<<16)
int pow2[N];
bool vis[S][2];
int ans[S];
int cnt, n, k;
void erule(int s) {
    for (int i = 0; i < 2; i++) {
        if (!vis[s][i]) {
            vis[s][i] = true;
            erule(((s << 1) + i) % pow2[n - 1]);
            ans[cnt++] = i;
        }
    }
}

int main() {
    pow2[0] = 1;
    for (int i = 1; i < N; i++)
        pow2[i] = pow2[i - 1] * 2;
    while (scanf("%d%d", &n, &k) != EOF && n + k) {
        memset(vis, 0, sizeof(vis));
        memset(ans, 0, sizeof(ans));
        cnt = 0;
        erule(0);

        cnt += n - 2 - k;
        int t = 0;

        for (int i = 0; i < n; i++)
            t = t * 2 + ans[cnt--];
        printf("%d\n", t);
    }
    return 0;
}

8.HDU - 2894 DeBruijin
这题跟上一题相似

#include 
#include 
#include 
using namespace std;
#define N 16
#define S (1<<16)
int pow2[N];
bool vis[S][2];
int ans[S];
int cnt, n, k;
void erule(int s) {
    for (int i = 0; i < 2; i++) {
        if (!vis[s][i]) {
            vis[s][i] = true;
            erule(((s << 1) + i) % pow2[n - 1]);
            ans[cnt++] = i;
        }
    }
}

int main() {
    pow2[0] = 1;
    for (int i = 1; i < N; i++)
        pow2[i] = pow2[i - 1] * 2;
    while (scanf("%d", &n) != EOF && n) {
        memset(vis, 0, sizeof(vis));
        memset(ans, 0, sizeof(ans));
        cnt = 0;
        erule(0);
        printf("%d ", pow2[n]);
        cnt += n - 2;
        for (int i = pow2[n]; i > 0; i--)
            printf("%d",ans[cnt--]);
                printf("\n");
    }
    return 0;
}

9.POJ - 1780 Code

这题的话。我就当个搬运工。附上大神的具体讲解
具体讲解

#include 
#include 
using namespace std;
#define N 1000010

char ans[N];
bool vis[N];
int pow10[8];
int n;

void solve() {
    if(n == 1) {
        printf("0123456789\n");
        return ;
    }
    memset(vis, 0, sizeof(vis));

    for (int i = 0; i < n; i++)
        ans[i] = '0';
    int cur = n, now = 0, i = 0;
    vis[0] = true;

    while (cur < pow10[n] + n - 1) {
        now %= pow10[n - 1];
        for (; i < 10; i++) {
            if (!vis[now * 10 + i]) {
                vis[now * 10 + i] = true;
                now = now * 10 + i;
                ans[cur++] = i + '0';
                i = 0;
                break;
            }
        }

        if (i == 10 && cur < pow10[n] + n - 1) {
            cur--;
            now = (ans[cur - n + 1] - '0') * pow10[n - 1] + now;
            vis[now] = false;
            i = now  % 10 + 1;
            now /= 10;
        }
    }
    ans[cur] = '\0';
    printf("%s\n", ans);
}

void init() {
    pow10[0] = 1;
    for (int i = 1; i < 8; i++)
        pow10[i] = pow10[i - 1] * 10;
}

int main(){
    init();
    while (scanf("%d", &n) != EOF && n) {
        solve();
    }
    return 0;
}

你可能感兴趣的:(图论--欧拉路,欧拉回路(小结))