2021 CCPC 女生賽
阿新 • • 發佈:2021-11-06
前兩週都沒怎麼訓練嗚嗚,作業太多了
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; }