十二桥问题(牛客CSP-S赛前集训营4)

题意:

链接:https://ac.nowcoder.com/acm/contest/1104/B

来源:牛客网
小多所在的城市可以看成是有n个点m条边的无向图(结点从1标号),每条边有一个距离,其中有k条边是小希特别想走过的k座大桥。

小多和小希现在呆在1号结点,请你帮小多规划一条最短路线,使得小多和小希能从当前位置出发,并经过这k座桥,最后回到结点1。
对于 100% 的数据,整张图联通,di≤1000000000
 
方法:在一个无向图中找一个经过节点1的最小环,使其必经k条边(k<=12)
看k的范围,易联想到状态压缩
12条边,24个点+节点1  一共25个必经点,其它点我们不必考虑,只需知道任意两点间的最短路dist
1.用25次dijkstra求出每个必经点到其它点的最短路
2.状压DP:dp[S][i]中S表示当前必经边的选择状态,i表示当前在节点i的最短路长度
枚举每一种状态,每一条边,每一个必经点,dp[S][u]=min(dp[last][j]+dist[j][v]+val(u,v))
初始化  dp[0][1]=0
目标  min(dp[S][i]+dist[i][1])
时间复杂度:O(nlogn+2^k)
code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=200005;
const int mod=998244353;
const int inf=0x7fffffff;

priority_queue<pair<int,int> >q;
int n,m,k,ans=1e18; //long long最大值
int Next[maxn*2],to[maxn*2],head[maxn],val[maxn*2],tot=0;
int dist[26][maxn],cnt=0;
int dp[(1<<12)][26],e[maxn];
bool vis[maxn];

void add(int x,int y,int z){
    to[++tot]=y; Next[tot]=head[x]; head[x]=tot; val[tot]=z;
}

int read(){
    int x=0,f=1; char c=getchar();
    while(c>'9'||c<'0'){
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c<='9'&&c>='0'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}

void dijkstra(int x){
    memset(vis,0,sizeof(vis));
    memset(dist[x],0x3f,sizeof(dist[x]));
    q.push(make_pair(0,e[x]));
    dist[x][e[x]]=0;
    while(!q.empty()){
        int now=q.top().second; q.pop();
        if(vis[now]) continue;
        vis[now]=1;
        for(int i=head[now];i;i=Next[i]){
            int y=to[i];
            if(dist[x][now]+val[i]<dist[x][y]){
                dist[x][y]=dist[x][now]+val[i];
                if(!vis[y]) q.push(make_pair(-dist[x][y],y));
            }
        }
    }
}

signed main(){
//    freopen("test.in","r",stdin);
    n=read();m=read(); k=read();
    e[++cnt]=1;
    for(int i=1;i<=m;i++){
        int x=read(),y=read(),z=read();
        add(x,y,z);
        add(y,x,z);
        if(i<=k){
            e[++cnt]=x;
            e[++cnt]=y;
        }
    }
    for(int i=1;i<=cnt;i++){
        dijkstra(i);
    }
    //dp[i][j]表示状态为i(桥),当前处于第j个点
    memset(dp,0x3f,sizeof(dp));
    dp[0][1]=0;//第0状态  在节点1无需花费
    for(int i=0;i<(1<<k);i++){
        for(int j=1;j<=k;j++){         //当前处于第j条边,且第j条边未取
            if(i&(1<<(j-1))) continue;
            int now=(i|(1<<(j-1)));   //将该位赋值1
            for(int t=1;t<=cnt;t++){
                dp[now][j*2]=min(dp[now][j*2],dp[i][t]+dist[t][e[j*2+1]]+val[j*2]);
                dp[now][j*2+1]=min(dp[now][j*2+1],dp[i][t]+dist[t][e[j*2]]+val[j*2]);
            }
        }
    }
    for(int i=1;i<=cnt;i++)  ans=min(ans,dp[(1<<k)-1][i]+dist[i][1]);
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xvzichen/p/11830707.html