CodeForces 21D 题解

题解笔记

CF21D Traveling Graph(Traveling.cpp)

时间限制 \(0.5s\) | 内存限制 \(64M\)

题目描述:

给定一个有 \(n\) 个顶点(编号从 \(1\)\(n\)),\(m\) 条带权边的无向图,要求从顶点 \(1\) 开始经过每一条边至少一次最后回到起点 \(1\),要求走过的边权值总和最小。注意:可能有重边和自环。

输入格式:

第一行 \(n\)\(m\)\(1 \leq n \leq 15\)\(0 \leq m \leq 2000\)),接下来的 \(m\) 行,每行包含三个数 \(u,v,w\)\(1 \leq w \leq 104\)),描述一条顶点 \(u\) 和顶点 \(v\) 之间有一条权值为 \(w\) 的无向边。

输出格式:

仅有一个整数 \(c\) ,表示最小的边权和,若无解则输出 −1 。

输入输出样例:

样例1输入 样例1输出 样例2输入 样例2输出
3 3
1 2 1
2 3 1
3 1 1
3 3 2
1 2 3
2 3 4
14

解题思路:

按照CodeForces上的标签,这道题的算法应该是图论和图匹配,尝试了一下,猜测大概正解应该是欧拉回路+匈牙利算法,但其实欧拉回路+暴力枚举优化完全可以过了。

我们只需要考虑重复走的路程就好了,答案就是重复走的路程+边权值之和

首先要先知道欧拉回路以及欧拉图

通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。

具有欧拉回路的无向图或有向图称为欧拉图。

对于无向图 \(G\)\(G\) 是欧拉图当且仅当 \(G\) 是连通的且没有奇度顶点。

考虑一下重复走的路程的情况,就是当图是欧拉图时,完全不重复走

于是,那重复走其实就是当你多走了这么多路后,图就变成了欧拉图。

很类似小奥一笔画问题中,将一个图添了多少笔后,就可以一笔画了

所以,接下来的任务就是消除所有入度为奇数的点

于是,就计算一遍全源最短路

然后暴力枚举,将入度为奇数的点两两配对,枚举所有的方案,取连边的边权值之和最小的方案,输出连边的边权值之和+边权值之和就好了(记得要判 \(-1\) 的情况,我用的是并查集)

关于暴力

以上代码实现步骤里,其实我个人感觉暴力是很重要的,因为我大部分时间都在优化暴力

暴力枚举 \(1.0\)

void dfs(int r,int cnt)
{
    if(r==tot>>1) ans=min(ans,cnt);
    else
    {
        for(int i=1; i<tot; i++)
        {
            if(!vis[i])
            {
                for(int j=i+1; j<=tot; j++)
                {
                    if(!vis[j])
                    {
                        vis[i]=vis[j]=1;
                        dfs(r+1,cnt+dis[od[i]][od[j]]);
                        vis[i]=vis[j]=0;
                    }
                }
            }
        }
    }
}

这个版本是 TLE 了的,时间复杂度应该是接近\(O(n^n)\) ,于是我就发现了可以优化 \(i\) 的初始值,将上一个 \(i\) 的初始值记录下来,这一次的 \(i\) 就从上一个的 \(i\)\(1\) 开始枚举,然后就可以优化为 \(O(n!)\) 的时间复杂度

暴力枚举 \(2.0\)

void dfs(int r,int cnt,int lmt)
{
    if(r==tot>>1) ans=min(ans,cnt);
    else
    {
        for(int i=lmt; i<tot; i++)
        {
            if(!vis[i])
            {
                for(int j=i+1; j<=tot; j++)
                {
                    if(!vis[j])
                    {
                        vis[i]=vis[j]=1;
                        dfs(r+1,cnt+dis[od[i]][od[j]],i+1);
                        vis[i]=vis[j]=0;
                    }
                }
            }
        }
    }
}

参考代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=17;
int dis[MAXN][MAXN],sum,deg[MAXN],ans=0x3f3f3f3f,od[MAXN],tot,vis[MAXN];
int beg[2005],en[2005];

void dfs(int r,int cnt,int lmt)
{
    if(r==tot>>1) ans=min(ans,cnt);
    else
    {
        for(int i=lmt; i<tot; i++)
        {
            if(!vis[i])
            {
                for(int j=i+1; j<=tot; j++)
                {
                    if(!vis[j])
                    {
                        vis[i]=vis[j]=1;
                        dfs(r+1,cnt+dis[od[i]][od[j]],i+1);
                        vis[i]=vis[j]=0;
                    }
                }
            }
        }
    }
}

int fa[MAXN];

int ufind(int x)
{
    while(x!=fa[x]) x=fa[x]=fa[fa[x]];
    return x;
}

int main()
{
    memset(dis,0x3f,sizeof dis);
    int n,m;
    scanf("%d%d",&n,&m);
    if(m==0)
    {
        printf("0\n");
        return 0;
    }
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1; i<=m; i++)
    {
        int w;
        scanf("%d%d%d",&beg[i],&en[i],&w);
        deg[beg[i]]++;
        deg[en[i]]++;
        sum+=w;
        dis[beg[i]][en[i]]=min(dis[beg[i]][en[i]],w);
        dis[en[i]][beg[i]]=min(dis[en[i]][beg[i]],w);
        fa[ufind(beg[i])]=fa[ufind(en[i])];
    }
    for(int i=1;i<=m;i++)
    {
        if(ufind(beg[i])!=ufind(1)&&(en[i])!=ufind(1))
        {
            printf("-1");
            return 0;
        }
    }
    for(int k=1; k<=n; k++)
    {
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            }
        }
    }
    for(int i=1; i<=n; i++)
    {
        if(deg[i]&1) od[++tot]=i;
    }
    dfs(0,0,1);
    printf("%d",sum+ans);
    return 0;
}

posted @ 2021-11-12 23:42  yhang323  阅读(66)  评论(1编辑  收藏  举报