Codeforces Round #826 (Div. 3) G - Kirill and Company(状压dp&&bfs)

题意:

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][sred[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) fk个有车的人,载人的方案再进行一次状压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(m2k)

状压的时间复杂度

  • O ( f ∗ 2 k ∗ 2 k ) O(f*2^k*2^k) O(f2k2k)
  • 子集dp的时间复杂度 O ( f ∗ 3 k ) O(f*3^k) O(f3k)

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());
    }
}

what’s more

红名大佬的Flow做法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值