2021CCPC女生赛 C.连锁商店 【思维 + 状压dp】

https://codeforces.com/gym/103389/problem/C

虽然 n 范围很小 (n <= 36), 但是直接枚举也会tle

可以想到,如果某家公司只有一家子公司,那么如果经过这家公司直接加上红包数额就好了

否则,某家公司有大于一家子公司,然而这样的公司的数量最多为 n / 2 家,于是可以枚举所有状态然后dp

// #pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 40;
int n, m;
int boss[N], w[N];
vector<int> v[N];
bool g[N][N], choose[N];
int tot, ind[N];
int dp[N][1 << 19];

// 子公司数量大于1的公司 最多 n / 2 家

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &boss[i]);
        v[boss[i]].push_back(i);
    }
    for(int i = 1; i <= n; i++) {
        if(v[i].empty()) continue;
        if(v[i].size() == 1) {
            choose[v[i][0]] = 1;  // 这家公司(的唯一子公司)被选定了
        }
        else { // 子公司size > 1 的公司 最多 n / 2 家
            ind[i] = tot;
            tot++;
        }
    }
    for(int i = 1; i <= n; i++) scanf("%d", &w[i]);

    for(int i = 1, u, v; i <= m; i++) {
        scanf("%d%d", &u, &v);
        g[u][v] = 1;
    }
    for(int k = 1; k <= n; k++) {   // 计算每两点之间是否可达,为后面剪枝
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                g[i][j] |= (g[i][k] & g[k][j]);
            }
        }
    }

    if(choose[1]) dp[1][0] = w[boss[1]];
    else dp[1][1 << ind[boss[1]]] = w[boss[1]];
    
    for(int u = 1; u <= n; u++) {
        for(int S = 0; S < (1 << tot); S++) {
            for(int v = 1; v <= n; v++) {
                if(!g[u][v]) continue;
                if(choose[v]) {
                    dp[v][S] = max(dp[v][S], dp[u][S] + w[boss[v]]);
                }
                else {
                    if( (S & (1 << ind[boss[v]])) == 0 ) {
                        dp[v][S | (1 << ind[boss[v]])] = max(dp[v][S | (1 << ind[boss[v]])], dp[u][S] + w[boss[v]]);
                    }
                }
            }
        }
    }
    
    for(int i = 1; i <= n; i++) {
        int ans = 0;
        for(int S = 0; S < (1 << tot); S++) {
            ans = max(ans, dp[i][S]);
        }
        printf("%d\n", ans);
    }
    
    system("pause");
    return 0;
}

好感慨啊。

如果去年多写出来这一题,就是银牌甚至可能金牌了。

好在今年已经具备了理解这样的题目的知识储备,今年一定要不负所望啊!!

posted @ 2022-10-03 16:55  starlightlmy  阅读(92)  评论(0编辑  收藏  举报