题目链接:点这里~
题目大意
- n颗宝石,m种关系ai,bi,说明宝石ai和bi可以放相邻的位置,然后给了k个宝石编号ci
- 问怎么合理串宝石,使得包含所有k个宝石,并且一共使用的宝石数量最少
- 范围1≤N≤1e5, 0≤M≤1e5, 1≤ai<bi≤N, 1≤K≤17, 1≤Ci≤N
- m对关系没有重复
思路
- bfs+状压dp
- 可以将这m个关系看成m条边,边的权值是1,然后就是构建一条最短路,使得跑完所有k个点ci
- 跑k个bfs,记录dis[i][x],表明点c[i]到点x的最短路径
- 接下来的任务就是跑完这所有k个点,如果按照顺序来的话就要全排列,17的阶乘的计算,太大了会t
- 那么就动态规划,状压dp,转移的是二进制的状态,dp[t][i]表示在t的状态下跑到点c[i]的最短路
- 然后就找k个点中还没走过的点j,加上点j之后的状态就是t+(1<<j)了,那么转移方程就是dp[t+(1<<j)][j] = max(dp[t+(1<<j)][j], dp[t][i] + dis[i][c[j]]);
- 最后状态就是二进制k个1,也就是(1<<k)-1,那么就更新dp[(1<<k)-1][i]的最小值即可
ac代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5 + 5;
const int inf = 0x3f3f3f3f;
int dis[20][maxn], c[20];
int dp[1<<17][20];
vector<int> v[maxn];
void bfs(int i){ //bfs跑最短路
int st = c[i];
queue<int> q;
q.push(st);
dis[i][c[i]] = 0;
while(q.size()){
int t = q.front(); q.pop();
for(auto it : v[t]){
if(dis[i][it] == inf){
dis[i][it] = dis[i][t] + 1;
q.push(it);
}
}
}
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++){
int x, y;
scanf("%d%d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
int k; scanf("%d", &k);
for(int i = 0; i < k; i ++) scanf("%d", &c[i]);
memset(dis, inf, sizeof(dis));
memset(dp, inf, sizeof(dp));
for(int i = 0; i < k; i ++){
bfs(i);
}
for(int i = 0; i < k; i ++){//初始点距离是0
dp[1<<i][i] = 0;
}
for(int t = 0; t < (1 << k); t ++){
for(int i = 0; i < k; i ++){
if(!((t >> i) & 1)) continue; //说明状态t中没有k个点中的第i个点,我们要跑到第i个点,所以这个状态不符合
for(int j = 0; j < k; j ++){
if((t >> j) & 1) continue;//说明状态t中有k个点中的第j个点, 现在要走到新点,所以跳
dp[t | (1 << j)][j] = min(dp[t | (1 << j)][j], dp[t][i] + dis[i][c[j]]); //dis表示k个点中第i个点跳到第j个点的最短路径
}
}
}
int ans = inf;
for(int i = 0; i < k; i ++){
ans = min(ans, dp[(1<<k) - 1][i] + 1); //算的是宝石的数量,我们求的是边的数量,所以还要加上初始点
}
if(ans == inf) ans = -1;
printf("%d\n", ans);
return 0;
}