给定一张$n$个点的带权无向图,点从$0∼n−1$标号,求起点$0$到终点$n−1$的最短 Hamilton 路径。
输入格式
第一行输入整数$n$。
接下来$n$行每行$n$个整数,其中第$i$行第$j$个整数表示点$i$到$j$的距离(记为$a[i,j]$)。
对于任意的$x,y,z$,数据保证$a[x,x]=0$,$a[x,y]=a[y,x]$ 并且$a[x,y]+a[y,z]≥a[x,z]$。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
$1≤n≤20$
$0≤a[i,j]≤10^7$
1 2 3 4 5 6 7 8 9 输入样例: 5 0 2 4 5 1 2 0 6 5 3 4 6 0 8 3 5 5 8 0 5 1 3 3 5 0 输出样例: 18
解题思路 一道经典的旅行商 NP 问题。
一共 20 个点,共 20!种排列方式,暴力搜素肯定行不通;
观察这样两条路径:
0 -> 1 -> 2 -> 3 长度为18
0 -> 2 -> 1 -> 3 长度为20
显然,长度为 20 的路径肯定不是最终答案,我们不需要考虑这条路径;
因此问题转化为考虑经过了哪些点后最终到达某个点的最短 Hamilton 路径长度。
$f[i][j]$:$i$是一个二进制数,表示到达$j$点经过了哪些点,显然$i$的第$j$位保证为$1$;
$f[i][j]$可以由所有的第$j$位为$0$,第$k$位为$1$的状态$f[stk][k]$转移而来。
AC代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 20 ;typedef long long LL;int w[N][N];LL f[1 << N][N]; int n;int main () { scanf ("%d" , &n); for (int i = 0 ; i < n; ++ i) for (int j = 0 ; j < n; ++ j) scanf ("%d" , &w[i][j]); memset (f,0x3f ,sizeof f); f[1 ][0 ] = 0 ; for (int i = 0 ; i < 1 << n; ++ i) for (int j = 0 ; j < n; ++ j) if (i >> j & 1 ) for (int k = 0 ;k < n; ++ k) if ((i - (1 << j)) >> k & 1 ) f[i][j] = min (f[i][j],f[i - (1 << j)][k] + w[k][j]); cout << f[(1 << n) - 1 ][n-1 ]; return 0 ; }
题目描述 $N$ 架飞机准备降落到某个只有一条跑道的机场。其中第$i$架飞机在$T[i]$时刻到达机场上空,到达时它的剩余油料还可以继续盘旋$D[i]$个单位时间,即它最早可以于$T[i]$时刻开始降落,最晩可以于$T[i] + D[i]$时刻开始降落。降落过程需要$L[i]$个单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断$N架$飞机是否可以全部安全降落。
输入格式 输入包含多组数据。
第一行包含一个整数$T$,代表测试数据的组数。
对于每组数据,第一行包含一个整数$N$。
以下$N$行,每行包含三个整数$T[i],D[i],L[i]$ 。
输出格式 对于每组数据,输出 YES
或者 NO
,代表是否可以全部安全降落。
样例 #1 样例输入 #1 1 2 3 4 5 6 7 8 9 2 3 0 100 10 10 10 10 0 2 20 3 0 10 20 10 10 20 20 10 20
样例输出 #1
提示 【样例说明】
对于第一组数据,可以安排第$3$架飞机于$0$时刻开始降落,$20$时刻完成降落。安排第$2$架飞机于$20$时刻开始降落,$30$时刻完成降落。安排第$1$架飞机于$30$时刻开始降落,$40$时刻完成降落。
对于第二组数据,无论如何安排,都会有飞机不能及时降落。
解题思路 $f[i][j]$表示$i$中数位为$1$的飞机已经降落且最后一架降落的飞机是$j$的最小落地时间 (显然保证$i$的第$j$位为$1$)
AC代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 10 , M = 1 << N;int f[M][N];int t[N],d[N],l[N];int n,T;int main () { cin >> T; while (T--) { scanf ("%d" , &n); for (int i = 0 ; i < n; ++ i) scanf ("%d%d%d" ,&t[i],&d[i],&l[i]); memset (f, 0x3f , sizeof f); for (int i = 0 ;i < n; ++ i) f[1 << i][i] = t[i] + l[i]; for (int i = 0 ; i < 1 << n; ++ i) for (int j = 0 ; j < n; ++ j) if (i >> j & 1 ) for (int k = 0 ; k < n; ++ k) if (i - (1 << j) >> k & 1 && f[i - (1 << j)][k] <= t[j] + d[j]) f[i][j] = min (f[i][j],max (t[j],f[i - (1 << j)][k]) + l[j]); int res = 0x3f3f3f3f ; for (int j = 0 ; j < n; ++ j) res = min (f[(1 << n) - 1 ][j],res); if (res == 0x3f3f3f3f ) puts ("NO" ); else puts ("YES" ); } return 0 ; }
众所周知,“八皇后” 问题是求解在国际象棋棋盘上摆放$8$个皇后,使得两两之间互不攻击的方案数。
已经学习了很多算法的小蓝觉得 “八皇后” 问题太简单了,意犹未尽。作为一个国际象棋迷,他想研究在$N \times M$的棋盘上,摆放$K$个马,使得两两之间互不攻击有多少种摆放方案。
由于方案数可能很大,只需计算答案除以$1000000007$(即$10^9+7$) 的余数。
如下图所示,国际象棋中的马摆放在棋盘的方格内,走 “日” 字,位于 (x,y) 格的马(第 x 行第 y 列)可以攻击 $(x+1,y+2)$、$(x+1,y−2)$、$(x−1,y+2)$、$(x−1,y−2)$、$(x+2,y+1)$、$(x+2,y−1)$、$(x−2,y+1)$ 和 $(x−2,y−1)$ 共 8 个格子。
输入格式 输入一行包含三个正整数$N,M,K$,分别表示棋盘的行数、列数和马的个数。
输出格式 输出一个整数,表示摆放的方案数除以$1000000007$(即$10^9+7$) 的余数。
数据范围 对于 $5\%$ 的评测用例,$K=1$;对于另外 10% 的评测用例,$K=2$; 对于另外 $10\%$ 的评测用例,$N=1$; 对于另外 $20\%$ 的评测用例,$N,M≤6$,$K≤5$; 对于另外 $25\%$ 的评测用例,$N≤3,M≤20,K≤12$; 对于所有评测用例,$1≤N≤6,1≤M≤100,1≤K≤20$。
1 2 3 4 5 6 7 8 9 10 11 12 输入样例1: 1 2 1 输出样例1: 2 输入样例2: 4 4 3 输出样例2: 276 输入样例3: 3 20 12 输出样例3: 914051446
解题思路 $dp$问题的显著特征就是可以把大问题化成小问题,再化零为整 ;
考虑棋子的放置过程,显然如果一列一列的放置棋子,显然棋子的放置只会受到前两列的影响(同一列棋子显然不会发生冲突);
于是我们考虑子问题:前$i$列已经放置完毕,并且第$i$列和第$i-1$列的状态分别是$a$、$b$,且已经放置了$j$枚棋子的方案数;
这时我们只需要顺序枚举每一列 $i$、$i - 1$ 列、 $i$ 列、$i - 2$列、棋子数目$j$即可;
最多循环次数$100 \times 64 \times 64 \times 64 \times 20$次;
这个数字大约是$5 \times 10^9$;
但是在枚举过程中状态之间需要保证不发生冲突,即$a$与$b$不能出现间隔$2$位为$1$的情况, $c$与$a$同理,并且$c$与$b$不能出现间隔$1$位为$1$的情况;
此外,$j$的枚举从$b$中有的$1$的个数开始,到$k$结束;
在实际运行中,大约运算了七百多万次。
AC代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 10 ,M = 110 ,mod = 1e9 + 7 ;int n,m,k;int f[M][1 << 6 ][1 << 6 ][30 ];int get (int x) { int res = 0 ; while (x) { ++ res; x -= x & -x; } return res; } int main () { cin >> n >> m >> k; f[0 ][0 ][0 ][0 ] = 1 ; for (int i = 1 ; i <= m; ++ i) for (int a = 0 ; a < 1 << n; ++ a) for (int b = 0 ; b < 1 << n; ++ b) { if (a & (b << 2 ) || b & (a << 2 )) continue ; for (int c = 0 ; c < 1 << n; ++ c) { if (a & (c << 2 ) || c & (a << 2 )) continue ; if (c & (b << 1 ) || b & (c << 1 )) continue ; int w = get (b),v = get (a); for (int j = v + w; j <= k; ++ j) for (int t = get (c) + v; t <= k - w; ++ t) f[i][a][b][j] = (f[i][a][b][j] + f[i-1 ][c][a][t]) } } int res = 0 ; for (int a = 0 ; a < 1 << n; ++ a) for (int b = 0 ; b < 1 << n; ++ b) res = (res + f[m][a][b][k]) % mod; cout << res << endl; return 0 ; }