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;
}
好感慨啊。
如果去年多写出来这一题,就是银牌甚至可能金牌了。
好在今年已经具备了理解这样的题目的知识储备,今年一定要不负所望啊!!