1. 程式人生 > 其它 >[題解][LG-P3943]星空

[題解][LG-P3943]星空

\(\Longrightarrow\)原題連結

很妙的一道題,本 \(\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...\)

。但是因為這裡的邊權都是 \(1\) ,所以直接使用 \(\mathrm{BFS}\) 就好了(每次選擇一個起始節點,然後暴力跑)。

\(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;
}