题意:
有
f
f
f个人从1出发,第
i
i
i个人,回他家
h
[
i
]
h[i]
h[i]
有
p
p
p个人没有汽车,
p
[
i
]
p[i]
p[i]对应上面
f
f
f的编号
有车的可以载没车的,但是每个有车的人回家都只能走最短路。
求最少的人回家走路。
思路:
有车的人只能走最短路,那我们先给图跑个BFS
红色的点,是没车的。
绿色的点,是有车的。
做顺风车,肯定是一条从1开始到绿色点的最短路径。
k不大,考虑状压dp。
- 一个红色的点可能有多个 人没车,输入的时候预处理下。
- 转移:
d p [ x ] [ s ∣ r e d [ x ] ] ∣ = d p [ x ] [ s ] dp[x][s|red[x]] |= dp[x][s] dp[x][s∣red[x]]∣=dp[x][s]
d p [ v ] [ s ] ∣ = d p [ x ] [ s ] dp[v][s] |= dp[x][s] dp[v][s]∣=dp[x][s]
这样我们就处理出了,以绿色的点为终点的且路径中包含红色点的方案。
最后就是让 ( f − k ) (f-k) (f−k)个有车的人,载人的方案再进行一次状压dp。(这里用背包的那种dp方法可以过,用子集dp也可以)
//有点像背包
for(int i = 0; i < f; i ++ ) {
if(not_have_car[i] == 1) continue;
for(int j = (1<<k) - 1; j >= 0; j --) {
for(int jj = (1<<k)-1; jj >= 0; jj--){
dp2[j|jj] |= (dp2[j]&dp1[h[i]][jj]);
}
}
}
//子集dp
for(int i = 0; i < f; i ++ ) {
if(not_have_car[i] == 1) continue;
//这种是枚举子集
for(int j = (1<<k)-1; j > 0; j -- ) {
for(int t = j; t > 0; t = (t-1)&j){
dp2[j] |= (dp2[j^t] & dp1[h[i]][t]);
}
}
}
最后统计ans就行了。
时间复杂度
bfs的时间复杂度: O ( m ∗ 2 k ) O(m*2^k) O(m∗2k)
状压的时间复杂度:
- O ( f ∗ 2 k ∗ 2 k ) O(f*2^k*2^k) O(f∗2k∗2k)
- 子集dp的时间复杂度 O ( f ∗ 3 k ) O(f*3^k) O(f∗3k)
AC(Java)
package com.hgs.codeforces.contest.div3.contest826.G;
/**
* @author youtsuha
* @version 1.0
* Create by 2022/10/11 22:35
*/
import java.util.*;
import java.io.*;
public class Main {
static FastScanner cin;
static PrintWriter cout;
private static void init()throws IOException {
cin = new FastScanner(System.in);
cout = new PrintWriter(System.out);
}
private static void close(){
cout.close();
}
static int n, m, f, k;
static List<Integer> e[];
static int not_have_car_sta[];
static int h[];
static int not_have_car[];
static int level[];
private static void sol()throws IOException {
n = cin.nextInt(); m = cin.nextInt();
e = new ArrayList[n];
for(int i = 0; i < m; i ++ ) {
int a = cin.nextInt(), b = cin.nextInt();
a--; b--;
if(e[a] == null) e[a] = new ArrayList<>();
if(e[b] == null) e[b] = new ArrayList<>();
e[a].add(b);
e[b].add(a);
}
f = cin.nextInt();
h = new int[f];
not_have_car = new int[f];
not_have_car_sta = new int[n];
for(int i = 0; i < f; i ++ ) {
h[i] = cin.nextInt();
h[i]--;
}
int dp1[][] = new int[n][65];//2^6 - 1, dp1 表示有nhc
k = cin.nextInt();
dp1[0][0] = 1;
for(int i = 0; i < k; i ++ ) {
int p = cin.nextInt();
p--;
not_have_car_sta[h[p]] |= (1<<i);
not_have_car[p]++;
}
/**
* bfs 建立分层图
*/
level = new int[n];
for(int i = 0; i < n; i ++ ) level[i] = n+100;
level[0] = 0;
Queue<Integer> que = new ArrayDeque<>();
que.add(0);
while(!que.isEmpty()){
int x = que.poll();
for(Integer v: e[x]){
if(level[v] == n+100){
level[v] = level[x] + 1;
que.add(v);
}
}
}
//遍历每条边,记录方案 O(m)
boolean in_que[] = new boolean[n];
que.add(0);
in_que[0] = true;
while(!que.isEmpty()){
int x = que.poll();
for(int i = (1<<k) - 1; i >= 0; i -- ) dp1[x][i|not_have_car_sta[x]] |= dp1[x][i];
for(Integer v: e[x]){
if(level[v] == level[x] + 1) {
if(!in_que[v]) {
que.add(v);
in_que[v] = true;
}
for(int i = (1<<k) - 1; i >= 0; i -- ) {
dp1[v][i] |= dp1[x][i];
}
}
}
}
int dp2[] = new int[65];
dp2[0] = 1;
for(int i = 0; i < f; i ++ ) {
if(not_have_car[i] == 1) continue;
for(int j = (1<<k) - 1; j >= 0; j --) {
for(int jj = (1<<k)-1; jj >= 0; jj--){
dp2[j|jj] |= (dp2[j]&dp1[h[i]][jj]);
}
}
//这种是枚举子集
/* for(int j = (1<<k)-1; j > 0; j -- ) {
for(int t = j; t > 0; t = (t-1)&j){
dp2[j] |= (dp2[j^t] & dp1[h[i]][t]);
}
}*/
}
int ans = k;
for(int i = (1<<k) - 1; i >= 0; i -- ) {
if(dp2[i] > 0) ans = Math.min(ans, k - builtinCount(i));
}
cout.println(ans);
}
private static int builtinCount(int i) {
int ans = 0;
while(i > 0){
if((i&1) == 1)ans++;
i /= 2;
}
return ans;
}
public static void main(String[] args) throws IOException {
init();
int t = cin.nextInt();
while(t-- > 0)sol();
close();
}
}
class FastScanner {
BufferedReader br;
StringTokenizer st = new StringTokenizer("");
public FastScanner(InputStream s) {
br = new BufferedReader(new InputStreamReader(s));
}
public FastScanner(String s) throws FileNotFoundException {
br = new BufferedReader(new FileReader(new File(s)));
}
public String next() throws IOException {
if (!st.hasMoreTokens())
st = new StringTokenizer(br.readLine());
return st.nextToken();
}
public int nextInt() throws IOException {
return Integer.parseInt(next());
}
public long nextLong() throws IOException {
return Long.parseLong(next());
}
public double nextDouble() throws IOException {
return Double.parseDouble(next());
}
}