08-08 題解
地址
A - CF1420E
luogu 翻譯更正
if he gives no more that k orders
對於至多 k 次操作, 題面沒有翻譯出來
思路
怎麼算貢獻?
貢獻 (被保護)出現在 「處在任意兩個不同的 0 的連續段的守衛」之間,而處於同一連續段的守衛之間沒有貢獻
設一共 \(cnt\) 個 \(0\), 每個連續段分別有 \(c_i\) 個 \(0\)
則 \(contribute = C_{cnt}^{2} - \sum_{i} C_{c_i}^{2}\)
考慮 DP
設 \(dp(i, j, k, l)\) 為考慮到第 \(i\) 個人, 前面一共進行了 \(j\) 步操作, 用了 \(k\) 個 \(1\) , 當前連續段有 \(l\) 個 \(0\)
但是這樣的空間複雜度是 \(O(n^5)\), 鐵定過不了
嘗試最佳化掉一維狀態
最後一維是最好最佳化的, 我們只需要把狀態的含義改為 「考慮到第 \(i\) 個人, 他選了 \(1\)」, 這樣只需要列舉一個 \(ii\ > i\) , 表示下一個選 \(1\) 的位置是 \(ii\) , 轉移即可。
而原來的 \(l\) 一維就等於 \(ii - i - 1\)
所以 \(dp(i, j, k) = min\{dp(ii < i\ , jj <= j\ , k - 1)\}\)
注意, 輸出答案時要取字首 \(min\), 原因見文首 「翻譯勘誤」
程式碼
\(O(n^5)\) 加一些剪枝, 跑的飛快
#include<bits/stdc++.h>
using namespace std;
const int N = 81, INF = 1e9;
int n;
int a[N], ps[N], cnt, cntzr;
int dp[N][N * N][N], ans[N * N];
int Calc(int x){
return x * (x - 1) / 2;
}
signed main(){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
if(a[i] == 1) ps[++cnt] = i;
else ++cntzr;
}
int up = Calc(n);
for(int i = 0; i <= n; i++){
for(int j = 0; j <= up; j++){
for(int k = 0; k <= n; k++){
dp[i][j][k] = INF;
}
}
}
for(int i = 1; i <= n; i++){
dp[i][abs(ps[1] - i)][1] = Calc(i - 1);
for(int j = 0; j <= up; j++){
for(int k = 1; k <= i && k < cnt; k++){
if(dp[i][j][k] == INF) continue;
int rem = cnt - k;
for(int ii = i + 1; ii <= n - rem + 1; ii++){
int jj = j + abs(ps[k + 1] - ii);
if(jj <= up){
dp[ii][jj][k + 1] = min(dp[ii][jj][k + 1], dp[i][j][k] + Calc(ii - i - 1));
}
}
}
}
}
for(int i = 0; i <= up; i++){
ans[i] = Calc(cntzr);
}
for(int j = 0; j <= up; j++){
if(j) ans[j] = ans[j - 1];
for(int i = cnt; i <= n; i++){
if(dp[i][j][cnt] == INF) continue;
ans[j] = min(ans[j], dp[i][j][cnt] + Calc(n - i));
}
}
for(int i = 0; i <= up; i++){
ans[i] = Calc(cntzr) - ans[i];
cout << ans[i] << " ";
}
cout << "\n";
}
B - AGC017D
吐槽 MX 給的題面有誤
思路
首先, 父節點的每個子樹都可以看成一個獨立的遊戲, 拆開, 發現形如 Nim 遊戲
遞迴地拆開後, 發現只需要處理父節點只有一個兒子的情況
但是這個遊戲比較特殊, 如果當前節點非根, 我們可以一步直接斷掉他和根的連邊, 結束這顆子樹中的遊戲
這一點在博弈狀態的 DAG 上表示為: 每個非根節點都指向 (SG = 0)
因為 SG(i) 表示 \(i\) 的子游戲的 MEX, 而 \(i\) 直接指向 \(0\) , 所以實際的 SG = SG(i) + 1(\(i\) 非根的情況下)
程式碼
#include<bits/stdc++.h>
#define int long long
#define Pii pair <int, int>
using namespace std;
const int N = 1e6 + 10, MOD = 20, INF = 1e18;
int n;
int u, v, sg[N];
vector <int> e[N];
void Dfs(int x, int y){
for(auto i : e[x]){
if(i == y) continue;
Dfs(i, x);
sg[x] ^= (sg[i] + 1);
}
}
signed main(){
cin >> n;
for(int i = 1; i < n; i++){
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
Dfs(1, 0);
if(sg[1]){
cout << "Alice\n";
}else{
cout << "Bob\n";
}
}
C - AGC052B
經典套路: 把邊權轉移到點權上
思路
設點權設為從根到他的路徑權值的抑或和
這樣你就發現了一些神奇的東西: 對於一條邊 \((u, v) dep_u < dep_v\), 操作這條邊相當於交換 \(u, v\) 點權, 其他點都不變
但是有特殊情況:根節點沒有連向父親的邊, 不妨假想一條
因為題目中的操作不會改變點權集合, 所以如果 \(w_2\) 中的那條虛擬邊和 \(w_1\) 中的一樣都是 \(0\) 的話, 兩個集合應該相等
將 \(w_1, w_2\) 的所有點權抑或起來就是 \(w_2\) 中虛擬邊的邊權, 將 \(w_2\) 中所有點權與他抑或後比較兩個集合是否相等即可
程式碼
#include<bits/stdc++.h>
#define int long long
#define Pii pair <int, int>
using namespace std;
const int N = 1e7 + 10, MOD = 998244353;
const int Inv2 = (MOD + 1) / 2;
int n;
int u, v, wa, wb;
int a[N], b[N];
struct Edge{
int v, wa, wb;
};
vector <Edge> e[N];
void Dfs(int x, int y, int va, int vb){
a[x] = va, b[x] = vb;
for(auto i : e[x]){
if(i.v == y) continue;
Dfs(i.v, x, va ^ i.wa, vb ^ i.wb);
}
}
signed main(){
cin >> n;
for(int i = 1; i < n; i++){
cin >> u >> v >> wa >> wb;
e[u].push_back({v, wa, wb});
e[v].push_back({u, wa, wb});
}
Dfs(1, 0, 0, 0);
int xr = 0;
for(int i = 1; i <= n; i++){
xr ^= (a[i] ^ b[i]);
}
for(int i = 1; i <= n; i++){
b[i] ^= xr;
}
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + n);
for(int i = 1; i <= n; i++){
if(a[i] != b[i]){
cout << "NO\n";
return 0;
}
}
cout << "YES\n";
}
D - CF1710E
二分圖博弈, 鴿
E - AGC040C
經典套路:黑白染色
思路
假設我們已經有了一個串, 將他的奇數位染成黑色, 偶數位染成白色
對於 \(AB, BA\) 他們一定是黑白各佔一個的
不妨把黑色位置的 \(A, B\) 都改成 \(A\) , 白色的都改成 \(B\) , 那麼原題中的限制就變成了 「不能刪除連續的 \(AA, BB\)」
那麼只有當 \(A\) 或 \(B\) 的數量超過一半時才沒法變成空串
方案數用組合數算一下就行
#include<bits/stdc++.h>
#define int long long
#define Pii pair <int, int>
using namespace std;
const int N = 1e7 + 10, MOD = 998244353;
const int Inv2 = (MOD + 1) / 2;
int n;
int tot;
int fac[N], inv[N];
void Init(){
int up = 1e7;
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
for(int i = 2; i <= up; i++){
fac[i] = fac[i - 1] * i % MOD;
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
}
for(int i = 2; i <= up; i++){
inv[i] = inv[i - 1] * inv[i] % MOD;
}
}
int C(int n, int m){
return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
int Q_pow(int a, int b){
int ans = 1, p = a;
while(b){
if(b & 1) ans = (ans * p) % MOD;
b >>= 1;
p = (p * p) % MOD;
}
return ans;
}
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
Init();
tot = Q_pow(3, n);
// cout << tot << "tot\n";
int st = (n + 1) / 2 + 1;
// cout << st << "st\n";
for(int i = st; i <= n; i++){
int num = C(n, i) * Q_pow(2, n - i) % MOD * 2 % MOD;
// cout << num << " ";
tot = (tot - num + MOD) % MOD;
}
cout << tot << "\n";
}
F - CF1642F
根號分治, 隨機化都行(題解區還有剛上新的同學的做法)
隨機化正確率的計算
如果模數是 20, 那麼不衝突的機率為 $\frac{C_{15}{5}}{C_{20}{5}} \approx 0.19369195046439628483 $
那麼我們做 20 次, 正確率就是 \(1 - (1 - 0.19369195046439628483) ^ {20} \approx 0.98650975275351626002\), 非常的高啊
程式碼
#include<bits/stdc++.h>
#define int long long
#define Pii pair <int, int>
using namespace std;
const int N = 1e6 + 10, MOD = 20, INF = 1e18;
int n, m, ans;
int a[N][7], w[N], num[N];
int dp[1 << 21], id[N];
int cnt;
unordered_map <int, int> rid;
void Calc(){
random_shuffle(id + 1, id + 1 + cnt);
int upp = (1 << 21) - 1;
for(int i = 0; i <= upp; i++){
dp[i] = INF;
}
for(int i = 1; i <= n; i++){
num[i] = 0;
for(int j = 1; j <= m; j++){
a[i][j] = id[a[i][j]];
num[i] |= (1 << (a[i][j] % MOD));
}
dp[num[i]] = min(dp[num[i]], w[i]);
}
for(int i = 0; i <= 20; i++){
for(int j = 0; j <= upp; j++){
if((j >> i) & 1){
dp[j] = min(dp[j], dp[j ^ (1 << i)]);
}
}
}
for(int i = 1; i <= n; i++){
ans = min(ans, w[i] + dp[upp ^ num[i]]);
}
}
signed main(){
srand(1ll * time(0) * time(0) % 998244353);
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
if(!rid[a[i][j]]) rid[a[i][j]] = ++cnt;
a[i][j] = rid[a[i][j]];
}
cin >> w[i];
}
for(int i = 1; i <= cnt; i++){
id[i] = i;
}
ans = INF;
for(int i = 1; i <= 20; i++){
Calc();
}
if(ans == INF){
ans = -1;
}
cout << ans << "\n";
}