2021 CCPC 女生赛

前两周都没怎么训练呜呜,作业太多了
http://codeforces.com/gym/103389

5道签到题就不放code了毕竟打完找不着了

C 连锁商店

状压DP,场上只能想到 \(O(2^n n^2)\)的做法,还觉得他卡不满,T了一发发现完全图就直接爆炸了。然后也不会优化,后来学到两种处理方式。

一种是题解的做法,将点分类讨论,对于只出现一次的商店不需要存入状态,而出现大于一次的商店总数不超过 \(n/2\),这样的做法是 \(O(2^{n/2} n^2)\)

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <vector>
typedef long long ll;
#define endl '\n'
#define P pair<int, int>
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;
const int N = 37;
const int INF = 0x3f3f3f3f;
#define int ll
vector<int> mp[N];
map<int, int> dp[N];

int cnt[N];
int n, m, c[N], w[N], cid[N];
signed main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> c[i], c[i]--, cnt[c[i]]++;
    for (int i = 0; i < n; i++) cin >> w[i];
    for (int i = 0, u, v; i < m; i++) {
        cin >> u >> v;
        mp[u].push_back(v);
    }

    int sn = 0;
    for (int i = 0; i < n; i++) {
        if (cnt[i] > 1) {
            cid[i] = sn++;
        }
    }

    if (cnt[c[1]] > 1) {
        dp[1][1 << cid[c[1]]] = w[c[1]];
    } else {
        dp[1][0] = w[c[1]];
    }
    for (int u = 1; u <= n; u++) {
        for (auto v : mp[u]) {
            if (cnt[c[v]] <= 1) {
                for (auto st : dp[u]) {
                    ll now = st.first;
                    dp[v][now] = max(dp[v][now], dp[u][now] + w[c[v]]);
                }
            } else {
                for (auto st : dp[u]) {
                    int now = st.first;
                    int id = cid[c[v]];
                    // bitset<5> a(now);
                    // cout << u << ": " << v << " " << c[v] << "|" << id << " "
                    //      << a << endl;
                    if ((now >> id) & 1) {
                        dp[v][now] = max(dp[v][now], dp[u][now]);
                    } else {
                        dp[v][now | (1 << id)] =
                            max(dp[v][now | (1 << id)], dp[u][now] + w[c[v]]);
                    }
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        int res = 0;
        for (auto st : dp[i]) {
            res = max(res, st.second);
        }
        cout << res << endl;
    }
}

另一种是很符合直觉的做法,很好想,但是我不会算复杂度。
注意到一个状态的所有子集一定没有它本身优,即 \(10110\) 最后的答案一定比 \(10111\) 要劣,所以可以每转移完一个点就将无用状态剔除一遍,具体操作就是这样

//处理st[now]
void del(int now) {
    vector<ll> tmp;
    for (int i = 0; i < st[now].size(); i++) {
        bool flag = 1;
        for (int j = 0; j < st[now].size(); j++) {
            if (i == j) continue;
            if ((st[now][i] | st[now][j]) == st[now][j]) {//如果i是任何一个集合的子集,则剔除
                flag = 0;
                break;
            }
        }
        if (flag) tmp.push_back(st[now][i]);
    }
    st[now] = tmp;
}
上一篇:luogu P4774 [NOI2018]屠龙勇士


下一篇:2019年 五月份ccpc湘潭、icpc西安南昌邀请赛总结