2021CCPC女生专场 C. 连锁商店 状压DP

本文解析了一道关于状压动态规划的经典练习题,题目要求从起点出发到达各个节点时能领取的最大红包金额总和。文章详细介绍了如何通过状态压缩减少复杂度,并给出了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

你现在在1号位置,给出若干单向路径,给出nnn个节点的所属公司,每个公司只能领取一次红包,问1号节点到每个节点所能领取到的累积最大金额,节点之间是独立的

  • 这是一道比较好的状压DPDPDP练习题,有一个思维点就是n≤36n\leq36n36,如果直接进行状压肯定不行,但是因为如果公司只出现了一次,那么直接选择就好了;如果公司出现次数多于1次,那么这一类公司的数量不会超过18,所以我们可以对这第二类公司进行状压
  • 我们设dp[s][i]dp[s][i]dp[s][i]表示当前已经选择(当前还没选)的第二类公司的状态为sss,当前选择到第iii个景点所获得的最大金额,book[i]book[i]book[i]表示第iii个景点属于第几个第二类公司,w[i]w[i]w[i]表示第iii个公司的红包金额,c[i]c[i]c[i]表示第iii个商店属于哪个公司
  • 首先确定初态,有两种情况,第一个景点属不属于第二类公司,如果属于第二类公司,那么有dp[1<<(book[1]−1)][1]=w[c[1]]dp[1<<(book[1]-1)][1]=w[c[1]]dp[1<<(book[1]1)][1]=w[c[1]],意思就是只选这一家,其他都不选;如果不属于,那么有dp[0][1]=w[c[1]]dp[0][1]=w[c[1]]dp[0][1]=w[c[1]]
  • 解决了初态,那么现在开始状态转移,转移策略就是如果当前景点属于第二类公司,那么根据dpdpdp方程的意义进行转移;如果不属于,那么直接选择即可,具体见代码
#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值