你现在在1号位置,给出若干单向路径,给出 n n n个节点的所属公司,每个公司只能领取一次红包,问1号节点到每个节点所能领取到的累积最大金额,节点之间是独立的
- 这是一道比较好的状压 D P DP DP练习题,有一个思维点就是 n ≤ 36 n\leq36 n≤36,如果直接进行状压肯定不行,但是因为如果公司只出现了一次,那么直接选择就好了;如果公司出现次数多于1次,那么这一类公司的数量不会超过18,所以我们可以对这第二类公司进行状压
- 我们设 d p [ s ] [ i ] dp[s][i] dp[s][i]表示当前已经选择(当前还没选)的第二类公司的状态为 s s s,当前选择到第 i i i个景点所获得的最大金额, b o o k [ i ] book[i] book[i]表示第 i i i个景点属于第几个第二类公司, w [ i ] w[i] w[i]表示第 i i i个公司的红包金额, c [ i ] c[i] c[i]表示第 i i i个商店属于哪个公司
- 首先确定初态,有两种情况,第一个景点属不属于第二类公司,如果属于第二类公司,那么有 d p [ 1 < < ( b o o k [ 1 ] − 1 ) ] [ 1 ] = w [ c [ 1 ] ] dp[1<<(book[1]-1)][1]=w[c[1]] dp[1<<(book[1]−1)][1]=w[c[1]],意思就是只选这一家,其他都不选;如果不属于,那么有 d p [ 0 ] [ 1 ] = w [ c [ 1 ] ] dp[0][1]=w[c[1]] dp[0][1]=w[c[1]]
- 解决了初态,那么现在开始状态转移,转移策略就是如果当前景点属于第二类公司,那么根据 d p dp dp方程的意义进行转移;如果不属于,那么直接选择即可,具体见代码
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
vector<int> c(n + 1);
vector<int> w(n + 1);
vector<vector<int> > g(n + 1);
map<int, int> mp, book;
for(int i=1;i<=n;i++){
cin >> c[i];
mp[c[i]] += 1;
}
int st = 0;
for(int i=1;i<=n;i++){
if(mp[c[i]] >= 2){
if(!book.count(c[i])){
st += 1;
book[c[i]] = st;
}
}
}
for(int i=1;i<=n;i++) cin >> w[i];
while(m--){
int u, v;
cin >> u >> v;
g[u].push_back(v);
}
st = (1 << st);
vector<vector<int> > dp(st, vector<int> (n + 1, -1));
vector<int> ans(n + 1);
if(!book.count(c[1])) dp[0][1] = w[c[1]];
else{
dp[1 << (book[c[1]] - 1)][1] = w[c[1]];
}
for(int s=0;s<st;s++){
for(int i=1;i<=n;i++){
if(dp[s][i] == -1) continue;
for(auto j : g[i]){
if(!book.count(c[j])){
dp[s][j] = max(dp[s][j], dp[s][i] + w[c[j]]);
continue;
}
if(s & (1 << (book[c[j]] - 1))){
dp[s][j] = max(dp[s][j], dp[s][i]);
}else{
dp[s ^ (1 << (book[c[j]] - 1))][j] = max(dp[s ^ (1 << (book[c[j]] - 1))][j], dp[s][i] + w[c[j]]);
}
}
ans[i] = max(ans[i], dp[s][i]);
}
}
for(int i=1;i<=n;i++){
cout << ans[i] << '\n';
}
return 0;
}