[状压DP][欧拉回路]吃货JYY

本文探讨了一个特定的旅行问题,即如何从南京出发并利用指定的航班返回,同时尽可能减少旅行成本。文章提出了一种解决方案,通过构建图模型并采用特定的状态表示法来解决该问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

世界上一共有N个JYY愿意去的城市,分别从1编号到N。JYY选出了K个他一定要乘坐的航班。除此之外,还有M个JYY没有特别的偏好,可以乘坐也可以不乘坐的航班。
一个航班我们用一个三元组(x,y,z)来表示,意义是这趟航班连接城市x和y,并且机票费用是z。每个航班都是往返的,所以JYY花费z的钱,既可以选择从x飞往y,也可以选择从y飞往x。
南京的编号是1,现在JYY打算从南京出发,乘坐所有K个航班,并且最后回到南京,请你帮他求出最小的花费。

Input
输入数据的第一行包含两个整数N和K;
接下来K行,每行三个整数x,y,z描述必须乘坐的航班的信息,数据保证在这K个航班中,不会有两个不同的航班在同一对城市之间执飞;
第K+2行包含一个整数M;
接下来M行,每行三个整数x,y,z 描述可以乘坐也可以不乘坐的航班信息。

Output
输出一行一个整数,表示最少的花费。数据保证一定存在满足JYY要求的旅行方案。

Sample Input
6 3
1 2 1000
2 3 1000
4 5 500
2
1 4 300
3 5 300

Sample Output
3100

Data Constraint
对于10%的数据满足N≤4;
对于30%的数据满足N≤ 7;
对于额外30%的数据满足,JYY可以只通过必须乘坐的K个航班从南京出发到达任意一个城市;
对于100%的数据满足2≤N≤13,0≤K≤78,2 ≤M ≤ 200,1 ≤x,y ≤N,1 ≤z ≤ 10^4。

Hint
样例说明:一个可行的最佳方案为123541。
机票所需的费用为1000+1000+300+500+300=3100。

分析

我们发现这是一个要求从1出发经过必经边回到1的最短路径,容易想到欧拉回路
那么如何处理必经边呢?
容易想到只把必经边加入图,然后度为奇数的点连接它们之间的最短路。
于是容易想到fi,i是一个二进制状态0表示度偶1表示度奇
但是不对劲!再一看题,这可没说是整个图是连通图啊
然后变成三进制状态,0表示图外,1表示图内度奇,0表示图内度偶
然后就很简单了,配对啥的= =

#include <iostream>
#include <cstdio>
#include <queue>
#include <memory.h>
#define rep(i,a,b) for (i=a;i<=b;i++)
const int N=14;
const int M=80;
using namespace std;
int n,k,m;
struct Edge {
    int u,v,w,nx;
}g[2*M];
int cnt,list[N];
int b[N]={1,3,9,27,81,243,729,2187,6561,19683,59049,177147,531441,1594323},p[N]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192};
int f[4782969],bf[16384],d[N][N],iod[N];

void Add(int u,int v,int w) {
    g[++cnt].u=u;g[cnt].v=v;g[cnt].w=w;g[cnt].nx=list[u];list[u]=cnt;
}

void Floyd() {
    int i,j,k;
    rep(i,1,n)
    rep(j,1,n)
    rep(k,1,n)
    d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

void Prepare() {
    int i,j,k;
    memset(bf,0x3f,sizeof bf);
    bf[0]=0;
    rep(i,0,(1<<n)-1)
    rep(j,1,n-1)
    if (!(i&p[j-1]))
    rep(k,j+1,n)
    if (!(i&p[k-1]))
    bf[i^p[j-1]^p[k-1]]=min(bf[i^p[j-1]^p[k-1]],bf[i]+d[j][k]);
}

void Get_connected() {
    queue<int> q;
    int a[N];
    while (!q.empty()) q.pop();
    memset(f,0x3f,sizeof f);
    f[2]=0;q.push(2);
    while (!q.empty()) {
        int s=q.front();q.pop();
        int i,j,k=0;
        rep(i,1,n)
        if (s/b[i-1]%3) a[++k]=i;
        rep(i,1,n)
        if (!(s/b[i-1]%3)) {
            for (j=list[i];j;j=g[j].nx)
            if (s/b[g[j].v-1]%3) {
                int ns=s+b[i-1]*2;
                if (f[s]>=f[ns]) continue;
                if (f[ns]==1061109567) q.push(ns);
                f[ns]=f[s];
            }
            rep(j,1,k) {
                int ns=s+b[i-1];
                ns+=(s/b[a[j]-1]%3==1)?b[a[j]-1]:-b[a[j]-1];
                if (f[s]+d[i][a[j]]>=f[ns]) continue;
                if (f[ns]==1061109567) q.push(ns);
                f[ns]=f[s]+d[i][a[j]];
            }
        }
    }
}

int Get_couple() {
    int s,ns,i,ans=2147483647;
    rep(s,0,b[n]-1) {
        bool a=0;
        rep(i,1,n)
        if (list[i]&&!(s/b[i-1]%3)) {
            a=1;
            break;
        }
        if (a) continue;
        int expect=s;
        rep(i,1,n)
        if (iod[i]&1) expect+=(s/b[i-1]%3==1)?b[i-1]:-b[i-1];
        ns=0;
        rep(i,1,n)
        if (expect/b[i-1]%3==1) ns^=p[i-1];
        ans=min(ans,f[s]+bf[ns]);
    }
    rep(i,1,cnt)
    if (i&1) ans+=g[i].w;
    return ans;
}

int main() {
    int i;
    scanf("%d%d",&n,&k);
    memset(d,0x3f,sizeof d);
    rep(i,1,k) {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        iod[u]++;iod[v]++;
        Add(u,v,w);Add(v,u,w);
        d[u][v]=d[v][u]=w;
    }
    scanf("%d",&m);
    rep(i,1,m) {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        d[u][v]=d[v][u]=min(d[u][v],w);
    }
    Floyd();
    Prepare();
    Get_connected();
    printf("%d",Get_couple());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值