海贼王之伟大航路
题意:
一共 n 个点,给定任意两点的距离
d
i
s
[
i
,
j
]
dis[i, j]
dis[i,j]。
问,从点 1 到点 n,中途所有节点经过且仅经过一次,距离最短为多少?
(
2
<
n
≤
16
)
(2 < n ≤ 16)
(2<n≤16)
思路:
点数很小,可以暴搜。
但是暴搜也有技巧,如何较高效率的暴搜?剪枝!
每个节点经过且仅经过一次,那么就是初始为1,终点为n的全排列。
但是直接搜的话复杂度为 O(n!),考虑剪枝。
一开始的做法是这样的,递归中用一个小技巧 swap 操作,对于一个位置,不知道选择哪个数,那么一般就是要遍历所有数,看哪个没有选过就选哪个,然后将这个标记。这个 swap 的思路是,首先将所有选择存到数组中,对于当前位置 u,将当前数组位置上的数和 u~n 位置上的数调换,然后递归。递归之后恢复现场再调回来。这样每次判断就只需要枚举 u~n 位置,降低了一些复杂度。
然后加上一个小剪枝,如果当前距离之和大于已经得到的答案了,那么就没有必要往后搜了,直接退出。
但是,还是没有搞过去hh。
还是需要采取更强的剪枝!
对于DFS中的剪枝,有几种常用的方法:
- 如果当前答案已经劣于之前得到的较优答案了,而之后的搜索会使得当前答案更劣,那么就没有必要继续搜下去了;
- 如果当前的答案加上后面最优策略搜索所能得到的答案都已经劣于之前得到的较优答案,那么也没有必要继续搜;
- 记忆化搜索,如果之前搜索这个位置,并且现在搜到这位置的答案没有之前搜到这位置时的答案优,那么就没有继续的必要了。
这里用的是记忆化搜索。
记录之前搜到每个点时的状态,需要用二进制进行状态压缩。
用 f[x, state]
表示,走到点 x 时,经过的点的状态为 state 的最优答案。
如果后面的搜索到这个点时,发现之前和当前相同状态时的答案比当前答案更优,那么当前的搜索就没有意义了,肯定不会使最终的答案更优。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 20, mod = 1e9+7;
int T, n, m, k;
int f[N][(1<<17)+10], dis[N][N]; //注意定义状态大小的时候要比给的n多1,因为最终所有位都为1,大小为1<<(N+1)-1。
int ans, state;
int p[N], sum;
int endd;
void dfs(int x)
{
if(x==n)
{
if(state != endd) return;
ans = min(ans, sum);
return;
}
if(sum >= ans || (f[x][state] && sum >= f[x][state])) return;
f[x][state] = sum;
for(int i=2;i<=n;i++)
{
if(state & (1ll<<i)) continue;
state += 1ll<<i;
sum += dis[x][i];
dfs(i);
sum -= dis[x][i];
state -= 1ll<<i;
}
}
signed main(){
Ios;
// while(cin>>n)
// {
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>dis[i][j];
ans = 2e9;
sum = 0;
state = 1ll<<1;
for(int i=1;i<=n;i++) endd += 1ll<<i;
dfs(1);
cout << ans << endl;
// }
return 0;
}
但是为什么定义 f[u, state]
为:走 u 步,状态为 state 时的最优答案,不对呢?
#include<bits/stdc++.h>
using namespace std;
const int N = 20, mod = 1e9+7;
int T, n, m, k;
int f[N][(1<<17)+10], dis[N][N];
int ans, state;
int p[N], sum;
void dfs(int u)
{
if(u==n+1)
{
if(p[n]!=n) return;
ans = min(ans, sum);
return;
}
if(sum >= ans || (f[u][state] && sum >= f[u][state])) return;
f[u][state] = sum;
for(int i=2;i<=n;i++)
{
if(state & (1ll<<i)) continue;
state += 1ll<<i;
p[u] = i;
sum += dis[p[u-1]][p[u]];
dfs(u+1);
sum -= dis[p[u-1]][p[u]];
state -= 1ll<<i;
}
}
signed main(){
Ios;
// while(cin>>n)
// {
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>dis[i][j];
ans = 2e9;
sum = 0;
p[1] = 1;
state = 1ll<<1;
dfs(2);
cout << ans << endl;
// }
return 0;
}
这是在 N≤16 的条件下,但是如果把 N 放到20,这种做法就超时了。
正解是 状压dp,有时间要学一下!