题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4479

Description

【故事背景】
作为JSOI的著名吃货,JYY的理想之一就是吃遍全世界的美食。要走遍全世界当然需要不断的坐飞机了。而不同的航班上所提供的餐食是很不一样的:比如中国的航班会提供中餐,英国的航班有奶茶和蛋糕,澳大利亚的航班有海鲜,新加坡的航班会有冰激凌……JYY选出了一些他特别希望品尝餐食的航班,希望制定一个花费最少的旅游计划,能够从南京出发,乘坐所有这些航班并最后回到南京。

【问题描述】

世界上一共有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描述可以乘坐也可以不乘坐的航班信息。
2<=N<=13,0<=K<=78,2<=M<=200,1<=x,y<=N,1<=z<=10^4

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
一个可行的最佳方案为1->2->3->5->4->1。
机票所需的费用为1000+1000+300+500+300=3100
 

题目大意,给一张有权无向图,标记其中的某些边。从点1出发要求我们找一条回路,且这条回路经过所有的标记边,最小化消耗的费用

考虑回路的性质,因为是在无向图中,只要我们的边集里的每个点度数都是偶数,那么就一定有一种方法形成回路

于是我们就枚举一个联通子图,注意到n的值很小,313≈1600000。咳咳,这是完全没有问题的。然后我们考虑从结点1开始向点集里面加点。状态压缩的时候,我们定义0表示当前点不在点集里,1表示在点集里且度数为奇数,2表示在点集里且度数是偶数。加点的时候我们有两种情况:

1.这个点是一条必须边的端点,且另一个端点在点集里,那么这条边我们一定要加进去。但注意我们此时并不用添加代价,所有必须边的代价放到最后加入(代价同时也包括点的度数)

2.枚举点集中的点,这个点与枚举的点存在一条路径,我们取最短路添加进去就好(最短路在之前用floyd先预处理好),同时我们要算上最短路的代价,改变点度数的奇偶性。值得注意的是,读者可能会有以下两个疑惑:

  ①要是最短路路径上的点不在我们枚举的点集里面怎么办?首先我们要明确,在连上最短路的时候,路径上的点奇偶性是不会改变的。其次,就算有些点不在点集里,我们要的只是当前这个状态,换言之就是这个结        果,不需要考虑中间的过程。而在之后的操作里我们也发现还需要的代价值取决于度数为奇数的点,路径上的点不会有影响。

  ②要是最短路经过了点集里的必须边呢?我们是不是多算了代价?这些边的端点的奇偶性是不是也变了?其实都没有,其实这道题说是欧拉回路的话我们的定义还是存在偏差,可以理解为我们重复经过了这个点,或者说多连了两条边在上面,奇偶性没有影响。再者航班是来回几次就要付出多少倍的钱的。读者模拟几张图发现出现上述情况的话我们的回路都必须绕回这个点,因此最短路付出的代价是必要的。

接下来我们其实就得到每一个状态形成的最小代价,但是这样还不行,我们还要让每一个点的度数都是偶数。于是我们必须把奇数点两两匹配,这个的代价我们也可以用g数组预处理好。(若是出现疑问可以参考上面的①②点),当然别忘了我们还有必须边的代价没有计算,此时一起算上去,ans取min值就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std; const int maxn=;
const int mxn=+;
const int inf=0x3f3f3f3f;
int n,m,tot,ans;
int head[maxn],bin[maxn],pin[maxn],g[<<maxn],dist[maxn][maxn],f[],deg[maxn],a[maxn];
struct EDGE
{
int to;int next;int l;
}edge[mxn<<];
inline int read()
{
char ch=getchar();
int s=,f=;
while (!(ch>=''&&ch<='')) {if (ch=='-') f=-;ch=getchar();}
while (ch>=''&&ch<='') {s=(s<<)+(s<<)+ch-'';ch=getchar();}
return s*f;
}
void init()
{
memset(g,inf,sizeof(g));
memset(dist,inf,sizeof(dist));
memset(f,inf,sizeof(f));
for (int i=;i<=n;i++) dist[i][i]=;
bin[]=pin[]=;
for (int i=;i<=n;i++) bin[i]=bin[i-]*,pin[i]=pin[i-]*;
}
void add(int x,int y,int l)
{
edge[++tot]=(EDGE){y,head[x],l};
head[x]=tot;
edge[++tot]=(EDGE){x,head[y],l};
head[y]=tot;
}
void floyd()//最短路
{
for (int k=;k<=n;k++)
for (int i=;i<=n;i++)
for (int j=;j<=n;j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}
void pre_dp()//需处理奇数点匹配的代价
{
g[]=;
for (int i=;i<bin[n];i++)
for (int j=;j<=n;j++)
if (!(i&bin[j-]))
for (int k=j+;k<=n;k++)
if (!(i&bin[k-]))
g[i^bin[j-]^bin[k-]]=min(g[i^bin[j-]^bin[k-]],g[i]+dist[j][k]);
}
void dp()//枚举状态,并转移
{
queue <int> q;
f[]=;q.push();
while (!q.empty())
{
int s=q.front();q.pop();
int cnt=;
for (int i=;i<=n;i++) if (s/pin[i-]%>) a[++cnt]=i;
for (int i=;i<=n;i++)
if (s/pin[i-]%==)//注意三进制状态压缩的方法
{
for (int j=head[i];j;j=edge[j].next)
if (s/pin[edge[j].to-]%>)
{
int s1=s+pin[i-]*;//必须点的度数一开始不算,视为0
if (f[s]>=f[s1]) continue;
if (f[s1]==inf) q.push(s1);
f[s1]=f[s];//更新
}
for (int j=;j<=cnt;j++)
{
int s1=s+pin[i-];
s1+=(s/pin[a[j]-]%==)?pin[a[j]-]:-pin[a[j]-];//改变度数
if (f[s]+dist[i][a[j]]>=f[s1]) continue;
if (f[s1]==inf) q.push(s1);
f[s1]=f[s]+dist[i][a[j]];
}
}
}
}
void calc()//统计答案
{
ans=inf;
for (int s=;s<=pin[n]-;s++)
{
int flag=;
for (int i=;i<=n;i++) if (deg[i]&&s/pin[i-]%==) {flag=;break;}//如果就必须点没加就直接跳过这个状态
if (flag) continue;
int now=s;
for (int i=;i<=n;i++) if (deg[i]&) now+=(s/pin[i-]%==)?pin[i-]:-pin[i-];//考虑必须边对必须点的度数影响
int s1=;
for (int i=;i<=n;i++) if (now/pin[i-]%==) s1^=bin[i-];
ans=min(ans,f[s]+g[s1]);
}
for (int i=;i<=tot;i+=) ans+=edge[i].l;
}
int main()
{
n=read();m=read();
init();
while (m--)
{
int x=read(),y=read(),l=read();
dist[x][y]=dist[y][x]=min(dist[x][y],l);
deg[x]++;deg[y]++;
add(x,y,l);
}
m=read();
while (m--)
{
int x=read(),y=read(),l=read();
dist[x][y]=dist[y][x]=min(dist[x][y],l);
}
floyd();
pre_dp();
dp();
calc();
printf("%d\n",ans);
return ;
}

BZOJ4479 [JSOI2013] 吃货jyy 解题报告(三进制状态压缩+欧拉回路)的更多相关文章

  1. BZOJ4479 : [Jsoi2013]吃货jyy

    若$k\leq 15$,那么可以设$d[i][S]$表示经过了$S$集合的边,现在位于$i$点的最短路. 可以用Dijkstra算法在$O(n^22^k)$时间内求出. 否则若$k>15$,那么 ...

  2. hdu4064 三进制状态压缩 好题!

    还不太会做这类题,总之感觉有点难啊. 用深搜代替打表求出一行所有的可行状态,注意要进行剪枝 这是自己理解的代码,但是tle了 #include<bits/stdc++.h> using n ...

  3. hdu-3001 三进制状态压缩+dp

    用dp来求最短路,虽然效率低,但是状态的概念方便解决最短路问题中的很多限制,也便于压缩以保存更多信息. 本题要求访问全图,且每个节点不能访问两次以上.所以用一个三进制数保存全图的访问状态(3^10,空 ...

  4. HDU 3001 Travelling (三进制状态压缩 DP)

    题意:有 n 个city,能够选择任一城市作为起点,每一个城市不能訪问超过2次, 城市之间有权值,问訪问所有n个城市须要的最小权值. 思路:由于每一个城市能够訪问最多两次,所以用三进制表示訪问的状态. ...

  5. 三进制状态压缩DP(旅行商问题TSP)HDU3001

    http://acm.hdu.edu.cn/showproblem.php?pid=3001 Travelling Time Limit: 6000/3000 MS (Java/Others)     ...

  6. BZOJ 4479: [Jsoi2013]吃货jyy

    一句话题意:求必须包含某K条边的回路(回到1),使得总权值最小 转化为权值最小的联通的偶点 令F[i]表示联通状态为i的最小权值,(3^n状压)表示不在联通块内/奇点/偶点,连边时先不考虑必选的边的度 ...

  7. HDOJ-3001(TSP+三进制状态压缩)

    Traving HDOJ-3001 这题考察的是状态压缩dp和tsp问题的改编 需要和传统tsp问题区分的事,这题每个点最多可以经过两次故状态有3种:0,1,2 这里可以模仿tsp问题的二进制压缩方法 ...

  8. P6085-[JSOI2013]吃货JYY【状压dp,欧拉回路】

    正题 题目链接:https://www.luogu.com.cn/problem/P6085 题目大意 \(n\)个点的一张无向图,有\(k\)条必走边,\(m\)条其他边,求从\(1\)出发经过必走 ...

  9. hdu 3001 Travelling (三进制)【状压dp】

    <题目链接> 题目大意: 给出n个点和m条边,求经过所有点所需的最小花费,每个点最多经过两次. 解题分析: TSP问题类型,由于此题每个点有三种状态,所以采用三进制状态压缩,0.1.2 分 ...

随机推荐

  1. HDU 4329 Contest 3

    果然换个编译器就过了.总的来说,不难,不过就是处理一些空格.学习了一个新的类 istringstream可以按空格划分.然后,那条式子要理解. 式子的意义是: 找到一个串,该串在query中是第几个找 ...

  2. vue2 router中的 @ 符号表示src

    vue2 router中的 @ 符号表示src 学习了:https://segmentfault.com/q/1010000009549802 这个是webpack起的别名: 在build/webpa ...

  3. POJ-1785-Binary Search Heap Construction(笛卡尔树)

    Description Read the statement of problem G for the definitions concerning trees. In the following w ...

  4. 归并排序(Python)

    一.采用分治策略:将原问题划分成n个规模较小的但结构和原问题相同的子问题,递归解决这些子问题后合并各个结果从而得到原问题的解. 二.分治策略的步骤: 分解:将原问题分解成一系列子问题 解决:子问题粒度 ...

  5. 基于FPGA的跨时钟域信号处理——专用握手信号

    在逻辑设计领域,只涉及单个时钟域的设计并不多.尤其对于一些复杂的应用,FPGA往往需要和多个时钟域的信号进行通信.异步时钟域所涉及的两个时钟之间可能存在相位差,也可能没有任何频率关系,即通常所说的不同 ...

  6. php中echo什么时候用到\"这个符号

    php中echo什么时候用到\"这个符号 当在引号中用到引号时,为避免混乱用\" \称为转义符,表示后面的字符和原来程序语言里的语法符号含义不同 常见的转义有 \" \' ...

  7. CNN tflearn处理mnist图像识别代码解说——conv_2d参数解释,整个网络的训练,主要就是为了学那个卷积核啊。

    官方参数解释: Convolution 2D tflearn.layers.conv.conv_2d (incoming, nb_filter, filter_size, strides=1, pad ...

  8. POJ 1951 模拟

    思路: 坑爹模拟毁我一生 给两组数据: 输入: YOURE TRAVELING THROUGH ANOTHER DIMENSION A DIMENSION NOT OF SIGHT. 输出: YR T ...

  9. 关于注意力机制(《Attention is all you need》)

    深度学习做NLP的方法,基本上都是先将句子分词,然后每个词转化为对应的词向量序列.(https://kexue.fm/archives/4765) 第一个思路是RNN层,递归进行,但是RNN无法很好地 ...

  10. HDU 2196 Computer(经典树形DP)

    题意自己看(猜) 题解 这题很经典,就是记录dp[i][0/1/2]分别代表,从i点向下最大和次大深度,和向上最大深度. 然后转移就行了. 我的写法可能太丑了.死活调不出来,写了一个漂亮的 #incl ...