AtCoder Beginner Contest 338F - Negative Traveling Salesman【floyd+状态压缩dp】

本文主要是介绍AtCoder Beginner Contest 338F - Negative Traveling Salesman【floyd+状态压缩dp】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原题链接:https://atcoder.jp/contests/abc338/tasks/abc338_f

Time Limit: 6 sec / Memory Limit: 1024 MB

Score: 500 points、

问题陈述

有一个有N个顶点和M条边的加权简单有向图。顶点的编号为 1 到 N,i/th 边的权重为 Wi​,从顶点 Ui​ 延伸到顶点Vi​。权重可以为负,但该图不包含负循环。

确定是否存在至少访问每个顶点一次的行走。如果存在这样的行走,请找出所遍历的边的最小总权重。如果同一条边被多次遍历,则每次遍历都要加上这条边的权重。

这里的 "至少访问每个顶点一次的行走 "是指满足以下两个条件的顶点序列v1​,v2​,…,vk​:

  • 对于每个 i 有一条边从顶点 vi​ 延伸到顶点 vi+1​。
  • 对于每个 j (1≤j≤N) 都有 i (1≤i≤k) 这样的边 vi​=j 。

限制因素

  • 2≤N≤20
  • 1≤M≤N(N−1)
  • 1≤Ui​,Vi​≤N
  • Ui​\neqVi​
  • (Ui​,Vi​)\neq(Uj​,Vj​)
  • −1e6≤Wi​≤1e6
  • 给定图形不包含负循环。
  • 所有输入值均为整数。

输入输出描述

Sample Input 1Copy

3 4
1 2 5
2 1 -3
2 3 -4
3 1 100

Sample Output 1Copy

-2

按照 2→1→2→3 的顺序跟随顶点,可以访问所有顶点至少一次,所遍历的边的总重量为 (−3)+5+(−4)=−2。这是最小值。

Sample Input 2Copy

3 2
1 2 0
2 1 0

Sample Output 2Copy

No

没有至少访问所有顶点一次的行走。

Sample Input 3Copy

5 9
1 2 -246288
4 5 -222742
3 1 246288
3 4 947824
5 2 -178721
4 3 -947824
5 4 756570
2 5 707902
5 1 36781

Sample Output 3Copy

-449429

解题思路:

首先题目要求我们至少经过每个点一次,也就是说我们只要找到一条路径经过了每个点并且这条路径权值总和最小,也就是说我们并不关心每个点经过了几次,只要经过了就行了,例如如果我们找到了一条权值总和最小并且经过所有点至少一次的路径a->b->c->b->d,我们考虑的路径集合应该是a->b->c->d,,也就是说c->b->d中间的b只是c->d最短路径经过的一个点而已,也就是说我们只需要考虑走过每个点的顺序即可,确定了顺序之后,任意俩个点之间的距离应该使用最短路径,由于不存在负环,任意俩个点之间肯定是存在最短路径的,并且最短路径的长度肯定是确定的,这个题目点的个数最多只有20个,对于考虑哪种走的顺序总路径最短,我们可以考虑状态压缩dp,然后需要提前预处理任意俩个点之间的最短路,可以采用floyd算法才求任意俩点之间的最短路径,预处理之后状态压缩dp处理即可,具体分析见代码处。

状态压缩dp处理过程

状态定义:

定义f[i][j]表示当前走的点的集合为i,并且走过的最后一个点为j的所有方案的最短路径。

初始化:

f[1<<i][i]=0,当前集合只有一个点,就是起点。

状态转移:

当前走过的最后一个点是j,需要考虑倒数第二个点k,用来转移

f[i][j]=min(f[i^(1<<j][k]+d[k][j])

最终答案:

最终走过的点的集合肯定是(1<<n)-1,然后走过的最后一个点可以是任意一个点,所以答案就是min(f[(1<<n)-1][i]) (0<i<n)

时间复杂度:floyd预处理时间复杂度为O(n^3),n=20,这个时间大概是8e3,状态压缩dp处理时间复杂度为O((2^n)*n*n),这个时间大概为4e8,这个时间复杂度很高了,但是这个题目给了6s的时间,所以是可以过的,最终是时间复杂度为O((n^3)+(2^n)*n*n)。

空间复杂度:d数组空间为O(n^2),dp数组f空间为O((2^n)*n),最终空间复杂度为O((n^2)+(2^n)*n),空间大概是(20^2+(2^20)*20),大概是2e7*4/1e6=80M,这个题目给了1024M,空间肯定是足够的。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 25, M = 1 << 20, INF = 0x3f3f3f3f;int n, m;
int d[N][N], f[M][N];void floyd()
{for (int k = 0; k < n; k++)for (int i = 0; i < n; i++)for (int j = 0; j < n; j++){if (d[i][k] == INF || d[k][j] == INF)continue;d[i][j] = min(d[i][j], d[i][k] + d[k][j]);}
}
int main()
{cin >> n >> m;for (int i = 0; i < n; i++)for (int j = 0; j < n; j++)if (i == j)d[i][j] = 0;elsed[i][j] = INF;for (int i = 0; i < m; i++){int u, v, w;scanf("%d%d%d", &u, &v, &w);u--, v--;d[u][v] = w;    //题目说了不存在重边。所以这里可以直接赋值,如果存在重边应该取所有重边的最小值}//floyd预处理任意俩个点之间的最短路floyd();memset(f, 0x3f, sizeof f);//初始之起点for (int i = 0; i < n; i++)f[1 << i][i] = 0;//状态压缩dp处理过程for (int i = 0; i < 1 << n; i++)  //第一维枚举走过的点的集合for (int j = 0; j < n; j++)   //第二维枚举当前走的点的集合中的最后一个点{if (~i >> j & 1)    //当前最后一个点是j,那么当前走过的点的集合必须包含j,continue;for (int k = 0; k < n; k++){if (j == k)   //不可能自己走到自己continue;if (~i >> k & 1) //前一个点是k,所以当前走过的点的集合i中必须包含kcontinue;if (f[i ^ (1 << j)][k] == INF || d[k][j] == INF)  //INF表示某种状态不存在或者俩个点之间不存在边,不能用于转移continue;f[i][j] = min(f[i][j], f[i ^ (1 << j)][k] + d[k][j]);}}int ans = INF;//当前走过的点的集合肯定是(1<<n)-1,最后一个点可以是任意一个点for (int i = 0; i < n; i++)ans = min(ans, f[(1 << n) - 1][i]);if (ans == INF)   //不存在走过每个点至少一次的路径,输出Noputs("No");else         //存在走过每个点至少一次的路径,输出最短路径的长度cout << ans << endl;return 0;
}

这篇关于AtCoder Beginner Contest 338F - Negative Traveling Salesman【floyd+状态压缩dp】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/663268

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1565(状态压缩)

本人第一道ac的状态压缩dp,这题的数据非常水,很容易过 题意:在n*n的矩阵中选数字使得不存在任意两个数字相邻,求最大值 解题思路: 一、因为在1<<20中有很多状态是无效的,所以第一步是选择有效状态,存到cnt[]数组中 二、dp[i][j]表示到第i行的状态cnt[j]所能得到的最大值,状态转移方程dp[i][j] = max(dp[i][j],dp[i-1][k]) ,其中k满足c

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

uva 10099(floyd变式)

题意: 有一个导游要带着一群旅客从一个城市到达另一个城市,每个城市之间有最大的旅客流量限制。 问最少几趟能将这些旅客从一个城市搞到另一个城市。 解析: 用floyd找出最小流量中的最大边,然后次数就是   ceil(总人数 / 最大承载量 - 1),-1的意思是导游每次也要在车上。 ps.老司机哭晕在厕所 代码: #include <iostream>#includ

uva 10048(floyd变式)

题意: 求两个点之间经过的路径中最大噪声最小的值。 解析: floyd的变式,每次取g[i][k] g[k][j]中的大边与当前边g[i][j]比较,取小。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#includ

poj 1734 (floyd求最小环并打印路径)

题意: 求图中的一个最小环,并打印路径。 解析: ans 保存最小环长度。 一直wa,最后终于找到原因,inf开太大爆掉了。。。 虽然0x3f3f3f3f用memset好用,但是还是有局限性。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#incl

uva 10154 DP 叠乌龟

题意: 给你几只乌龟,每只乌龟有自身的重量和力量。 每只乌龟的力量可以承受自身体重和在其上的几只乌龟的体重和内。 问最多能叠放几只乌龟。 解析: 先将乌龟按力量从小到大排列。 然后dp的时候从前往后叠,状态转移方程: dp[i][j] = dp[i - 1][j];if (dp[i - 1][j - 1] != inf && dp[i - 1][j - 1] <= t[i]