状压DP

CF580D

题目链接
https://codeforces.com/problemset/problem/580/D
题目大意
image
思路
令dp[i][j]表示,吃菜状态为i,且最后一道菜为j的最大满足感!
代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 20;
int n, m, t, q;
int a[N],b[N * N][N * N];
ll dp[1 << N][N],ans;
// dp[i][j]表示状态为i时,吃最后一道菜为j时获得的最大满足感!
void solve() {
    cin >> n >> m >> q;
    for(int i = 0;i < n;++i) {
        cin >> a[i];
        dp[1 << i][i] = a[i];
    }
    for(int i = 0;i < q;++i){
        int x,y,c;cin >> x >> y >> c;
        --x;--y;
        b[x][y] = c;
    }
    for(int i = 0;i < (1 << n);++i){
        for(int j = 0;j < n;++j){
            // i 不 包 含 j
            if((i &(1 << j)) == 0) continue;
            for(int k = 0;k < n;++k){
                // i 中 不 包 含 k
                if(j == k || (!(i &(1 << k)))) continue;
                // j 从 k 转移过来
                dp[i][j] = max(dp[i][j],dp[i ^ (1 << j)][k] + a[j] + b[k][j]);
            }
            if(__builtin_popcount(i) == m){
                ans = max(ans,dp[i][j]);
            }
        }
    }
    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    t = 1;
    for (int _ = 0; _ < t; _++)
        solve();
    return 0;
}

CF11D

题目链接
https://codeforces.com/problemset/problem/11/D
题目大意
image
思路
默认状态i中的序号最小节点为起点,用lowbit表达!
代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 20;
int n, m, t;
bool g[N][N];
ll dp[1 << 20][N],ans;
void solve() {
    cin >> n >> m;
    for(int i = 0;i < m;++i){
        int a,b;cin >> a >> b;
        --a;--b;
        g[a][b] = g[b][a] = true;
    }
    for(int i = 0;i < n;++i) dp[1 << i][i] = 1;
    for(int i = 1;i < (1 << n);++i){
        for(int j = 0;j < n;++j){
            // i 状 态 下,最 后 一 个 到 达 节 点 j 的 方 案 数
            if(!dp[i][j]) continue;
            for(int k = 0;k < n;++k){
                // 节 点 j 无 法 到 达 节 点 k,或 者 i 状 态 的 起 点 大 于 节 点 k!
                if(!g[j][k] || (i & -i) > (1 << k)) continue;
                // 如 果 状 态 i 中 有 k 节 点
                if((1 << k) & i){
                    // 首 尾 相 连
                    if(1 << k == (i & -i)) ans += dp[i][j];
                }else{
                    // 将 节 点 k 加 入 状 态
                    dp[i | 1 << k][k] += dp[i][j];
                }
            }
        }
    }
    // 去除重复状态:①同一条路径出现两次【无 向 边】;②一条边和两个端点构成非法环。
    cout << (ans - m) / 2 << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    t = 1;
    for (int _ = 0; _ < t; _++)
        solve();
    return 0;
}

CF16E

题目链接
https://codeforces.com/problemset/problem/16/E
题目大意
image
代码

#include<bits/stdc++.h>

using namespace std;
const int N = 20;
int n, m, t;
double dp[1 << N],prob[N][N];

void solve() {
    cin >> n;
    for(int i = 0;i < n;++i){
        for(int j = 0;j < n;++j){
            cin >> prob[i][j];
        }
    }
    // 初始化,所有鱼都在的概率为 1
    dp[(1 << n) - 1] = 1;
    for(int i = (1 << n) - 1;i > 0;--i){
        double cnt = 0;
        // 有 多 少 只 活 着 的 鱼 ?
        for(int j = 0;j < n;++j){
            if(i & (1 << j)) ++cnt;
        }
        // 挑 选 状 态 i 里 活 着 的 鱼 j 与 k!
        for(int j = 0;j < n;++j){
            if(!(i & (1 << j))) continue;
            for(int k = j + 1;k < n;++k){
                if(!(i & (1 << k))) continue;
                // j 吃掉 k          活着的鱼状态为i的概率 * j吃掉k的概率 * (j 与 k 遇见的概率)
                dp[i - (1 << k)] += dp[i] * prob[j][k] * (1.0  / (cnt * (cnt - 1) / 2));
                // k 吃掉 j
                dp[i - (1 << j)] += dp[i] * prob[k][j] * (1.0 / (cnt * (cnt - 1) / 2));
            }
        }
    }
    cout << setiosflags(ios::fixed);
    for(int i = 0;i < n;++i){
        cout << setprecision(6) << dp[1 << i] << ' ';
    }
    cout << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    t = 1;
    for (int _ = 0; _ < t; _++)
        solve();
    return 0;
}

posted @ 2024-04-04 18:19  gebeng  阅读(19)  评论(0)    收藏  举报