[題解][LG-P3943]星空
很妙的一道題,本 \(\mathrm{juruo}\) 在考場上做不出來。思路錯了,乾瞪眼半天看不出做法。
因本人喜好,將原題的 \(0/1\) 調換了。(霧)
對區間進行操作,(可以看成)最後才觀察答案(如果是暴力),那麼可以(比較難)想差分。對區間 \([l,r]\) 取反,就是在差分陣列 \(f_{1...n+1}\) 中 \(f_l,f_{r+1}\) 分別取反,同時,當存在 \(a_i=p\) ,那麼 \(f_p,f_{p+1}\) 分別取反。那麼最終第\(i\)個位置的值就是 \(\bigoplus_{j=1}^i f_j\)。
接著可以想到,原陣列每一個位置都變成零和\(f\)
現在問題轉化成了:有 \(m\) 種間隔,每次操作選取一個間隔 \(b\) 和一個開始位置 \(st\) ,然後 \(f_{st}, f_{b+st}\) 分別取反,用最少的操作使 \(f\) 變成零。(廢話)
先考慮 \(n\leq 16\) 或者更小的情況。
可以想到,用一次或多次操作可以做出多種間隔,那麼我們設 \(val(i,j)\) 表示同時將 \(i,j\) 這兩個位置取反需要的最小步數。為了求出這個陣列,我們將問題轉化為一個最短路問題,將每一個位置看成圖上的一個點,而一個點 \(p\) 有最多 \(2m\) 條邊(因為有邊界),分別為 \(p \pm b_1, p \pm b_2, p \pm b_3...\)
設 \(d(S)\) 表示 \(f\) 的值的狀態壓縮,那麼可以使用 \(\mathrm{spfa}\) ,將一個狀態看成一個點,一條邊 \((S, S \oplus \{i\} \oplus \{j\})\) 的邊權就是 \(val(i,j)\) 。起始點就是 \(f\) 還沒有操作之前的狀態,答案就是 \(d(0)\) 。
但是對於這個部分分而言,直接使用 \(\mathrm{spfa}\) 就好了,這樣寫反而還可能會超時,但是上述的做法才能逐步拓展到 \(n \leq 4\times 10^4\)
考慮 \(n\leq 200\) 的情況。
\(val(i,j)\) 一樣可以求出來,但是 \(d\) 是求不了。(悲)
考慮每一次操作,對 \(f\) 中 \(1\) 的個數有三種變化:\(+2\),不變,\(-2\),顯然 \(+2\) 是不可能的,這輩子都不可能的。而對於不變的情況,是我可以看成是 \(-2\) 操作中的其中幾步,因為新增的那個 \(1\) 一定是要在之後被刪掉的。那麼我們在做 \(\mathrm{spfa}\) 的時候,每次轉移都要保證 \(i \in S, j \in S, i < j\) ,然後仔細想想,其實根本不需要 \(\mathrm{spfa}\) ,只需要一個 \(dp\) 就可以了,因為每個 \(S\) 只會向自己的真子集連邊。同時,我們已經不需要記住每一個位置的狀態,只需要記住 \(f\) 初始狀態中,是 \(1\) 的位置就好了。
考慮 \(n\leq 4\times 10^4\) 的情況。
因為我們轉移的時候只用到了 \(f\) 初始狀態是 \(1\) 的位置,所以 \(val(i,j)\) 更改為將 \(pos_i,pos_j\) 取反所需要的最小步數,其中 \(pos_i\) 表示 \(f\) 的初始狀態中第 \(i\) 個為 \(1\) 的位置。因為原陣列只有 \(k(k\leq 8)\) 個 \(1\) ,那麼 \(pos\) 的大小至多為 \(16\) 。但是我們在計算 \(val(i,*)\) 的時候依然要遍歷所有的點,所以計算 \(val(i,j)\) 的時間複雜度是 \(O(kmn)\) 。
那麼總的時間複雜度為 \(O(kmn+2^{k}k^2)\) 。
考慮\(k\leq 11\)的情況。
對於一個 \(dp\) 的狀態 \(S\) ,其第一個為 \(1\) 的位置是一定要變成 \(0\) 的(廢話),那麼我們在每一次轉移的時候都強制選擇第一個為 \(1\) 的位置作為 \(i\) ,然後再列舉配套的第二個位置。
那麼最終的時間複雜度為 \(O(nmk+2^k k)\) 。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e4 + 5, maxk = 15, maxm = 75;
int n, m, k, pos_sz, b[maxm], sta[maxn];
int pos[maxk << 1], val[maxk << 1][maxk << 1];
int dist[maxn], vis[maxn];
queue<int> q;
void calc_dfs() {
for (int st = 1; st <= pos_sz; st++) {
memset(dist, 0x3f, sizeof(dist)), memset(vis, 0, sizeof(vis));
q.push(pos[st]), vis[pos[st]] = 1, dist[pos[st]] = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = 1; i <= m; i++) {
int v = u + b[i];
if (v <= n + 1 && !vis[v]) {
dist[v] = dist[u] + 1, vis[v] = 1;
q.push(v);
}
v = u - b[i];
if (v >= 1 && !vis[v]) {
dist[v] = dist[u] + 1, vis[v] = 1;
q.push(v);
}
}
}
for (int ed = 1; ed <= pos_sz; ed++)
val[st][ed] = dist[pos[ed]];
}
}
int f[1 << (11 << 1)];
int main() {
scanf("%d%d%d", &n, &k, &m);
for (int i = 1; i <= k; i++) {
int p; scanf("%d", &p);
sta[p] ^= 1, sta[p + 1] ^= 1;
}
for (int i = 1; i <= m; i++) scanf("%d", &b[i]);
for (int i = 1; i <= n + 1; i++) if (sta[i])
pos[++pos_sz] = i;
calc_dfs();
memset(f, 0x3f, sizeof(f));
int maxS = (1 << pos_sz) - 1; f[maxS] = 0;
for (int S = maxS; S >= 1; S--) {
int p = 1;
while (!(S & (1 << (p - 1)))) p++;
for (int p2 = p + 1; p2 <= pos_sz; p2++) {
if (!(S & (1 << (p2 - 1)))) continue;
int nexS = S ^ (1 << (p - 1)) ^ (1 << (p2 - 1));
f[nexS] = min(f[nexS], f[S] + val[p][p2]);
}
}
printf("%d\n", f[0]);
return 0;
}