UOJ NOI Round #7 Day1 位元迷宮 個人記錄

SkyMaths發表於2024-11-13

思路

構造,且上界並不是特別嚴格。/bx/bx/bx

首先加法比較“混合”,考慮轉成位運算,具體地,欽定操作的 \(a, b\) 滿足 \(a\&b = 0\)

考慮遞迴成子問題,按照 popcount 分組,有一個關鍵觀察是:我們在操作一個 \(a|b = c\) 的時候,可以將任意幾個 \(d\&c = d\)\(popcount(d) = popcount(c) - 1\)\(a_d\) 01 翻轉。

具體而言,假設我們在操作 10111,我們要使得 10110 和 10101 01翻轉,那我們可以使 a|b = 10111,且 b = 00011,即我們要的 \(d\) 的缺的 1。

發現這個時候 < k - 1 的位上的 a 會受到一堆操作的影響,那我們直接先不管它,因為我們在操作 \(popcount = i\) 的時候 \(popcount > i\) 的是不會受影響的所以遞迴成子問題即可。

那麼我們考慮求出 \(T_{1\sim k}\) 使得 \(T_i\) 能覆蓋所有 \(popcount = i - 1\)。這個可以貪心求,每次求最大的可行的即可(可以用桶最佳化),不一定最優但是夠了,我構造出來的 \(\sum T_k = 159599\),不知道為啥,其他人好像可以構造出 157884。哦,他們選擇的是覆蓋最多的情況下最小的值加入 T 中,而我是隨便選的,不知道為什麼他們會更優/yun

有知道的老哥能不能教教我/kel

好像沒什麼細節,注意一開始若 \(a_{n - 1}\not= 1\) 的話要額外進行一次操作使得 \(a_{n - 1}\) 變為 1.

程式碼

const int N = 1 << 20;
const int LogN = 20;
int n, k, _, tot;
int a[N], vis[N], cnt[N], inT[N];
vector <int> pos[LogN + 1], val[LogN + 1], T[LogN + 1];

vector <pii> opt;
void answer(int a, int b) {
    assert((a & b) == 0);
    int s = b;
    do {
        Main::a[a | s] ^= 1;
    } while(s && (s = (s - 1) & b, 1));
    opt.eb(a, b);
}
void output() {
    printf("%llu\n", opt.size());
    for (pii _ : opt) {
        int a = _.fi, b = _.se;
        printf("%d %d\n", a, b);
    }
    fflush(stdout);
}

void skymaths() {
    read(n, k, _);
    for (int i = 0; i < n; ++i) read(a[i]);
    // read(k); n = 1 << k;
    for (int i = 0; i < n; ++i) {
        vis[i] = 1;
        pos[cnt[i] = __builtin_popcount(i)].emplace_back(i);
    }
    rep (c, 1, k) {
        // 求 T(c),即覆蓋所有 T(c - 1)
        for (int i = 0; i <= k; ++i) val[i].clear();
        for (int v : pos[c]) {
            val[c].eb(v);
            assert(cnt[v] == c);
            // printf("c = %d, v = %d\n", c, v);
        }
        per (i, k, 1) {
            for (int v : val[i]) {
                if (cnt[v] != i) {
                    // printf("    %d cnt -> %d\n", v, cnt[v]);
                    // assert(cnt[v] < i);
                    val[cnt[v]].eb(v);
                    continue;
                }
                T[c].eb(v); ++tot;
                inT[v] = 1;
                rep (j, 0, k - 1) {
                    if ((v >> j & 1) && vis[v ^ (1 << j)]) {
                        int t = v ^ (1 << j);
                        vis[t] = 0;
                        rep (bit, 0, k - 1) {
                            if (t >> bit & 1) continue;
                            --cnt[t ^ (1 << bit)];
                        }
                    }
                }
            }
        }
    }

	// 此時輸出 tot 就是 sum |Tk|

    if (!a[n - 1]) answer(n - 1, 0);

    per (i, k, 1) {
        for (int v : T[i]) {
            int msk = 0;
            rep (j, 0, k - 1) {
                if (v >> j & 1) {
                    if (a[v ^ (1 << j)] != inT[v ^ (1 << j)]) msk |= 1 << j;
                }
            }
            answer(v ^ msk, msk);
        }
    }

    rep (i, 0, n - 1) {
        if (a[i] != 0) {
            cerr << "Wrong on i = " << i << endl;
        }
    }

    output();
	
    // check 正確性用的程式碼
	
    // clr(vis);
    // rep (c, 1, k) {
    //     for (int v : T[c]) {
    //         rep (j, 0, k - 1) {
    //             if (v >> j & 1) {
    //                 vis[v ^ (1 << j)] = 1;
    //             }
    //         }
    //     }
    // }
    // rep (i, 0, n - 1) {
    //     if (!vis[i]) {
    //         printf("wrong on i = %d\n", i);
    //     }
    // }

    // printf("%d\n", tot);
    // return ;
    // rep (c, 1, k) {
    //     printf("T(%d) = \n", c);
    //     for (int v : T[c]) {
    //         printf("%d ", v);
    //     }
    //     printf("\n");
    // }
}

相關文章