Codeforces Round 940 (Div. 2)

加固文明幻景發表於2024-04-22

這場還挺Edu的

C. How Does the Rook Move?

Problem - C - Codeforces

  • 數學方法

​ 我用的數學方法,卡了很久才推出來思路和式子。

​ 首先行列其實等價,直接單考慮剩餘 \(n\) 行就行。

​ 這類題應該選擇先選一個東西,然後處理剩下的東西。

​ 這裡好做的方法是先選 \(m\)\((r, c)(r = c)\) 的格子,每次會耗費一個格子,後選 \(n - m\) \((r,c)(r\neq c)\)的格子,每次會耗費兩個格子。

​ 因為後選的格子每次會耗費兩個,所以要保證留給後選的數目必須為偶數。

​ 所以大體流程就是列舉所有的 \(m(m < n, (n - m)\mid 2)\),然後推式子:

​ 這裡推選 \(m\) 個的比較直接:\(\binom{n}{m}\)

​ 但是剩下的那個卡了我半小時:

​ 我們還剩下 \(n - m\) 行/列,而且必須是偶數(第2類棋不能走完奇數行/列)。

​ 我們可以看到,剩餘列的 \((n - m)!\) 每一種排列都對應著一組我們可以下的棋步。例如,如果我們還剩下 \((1, 4, 5, 6)\) 列,那麼 \((4, 5, 6, 1)\) 排列對應於下 \((4, 5), (6, 1)\) 步棋。然而,如果我們只是簡單地計算排列數,那麼我們也將計算排列 \((6, 1, 4, 5)\) ,它對應的是同一組棋步。

為了消除多算,我們可以用 \((n - m)!\) 除以 \(((n - m)/2)!\) (去除所選棋子對的排列)。

​ 因此,答案變為

\(\sum\limits_{c = 0}^m [(n - m) \bmod 2 = 0] {n \choose m} \frac{(n - m)!}{\left(\frac{n - m}{2}\right)!}\)

void solve()
{
#define tests
    int n, k;
    std::cin >> n >> k;
    std::vector<bool> vis(n);
    for (int i = 0, r, c; i < k; i++) {
        std::cin >> r >> c;
        --r, --c;
        vis[r] = vis[c] = true;
    }
    int cntNotVis(std::count(all(vis), false));
    Z ans {};
    for (int m = cntNotVis & 1; m <= cntNotVis; m += 2) {
        ans += comb.binom(cntNotVis, m) * comb.fac(cntNotVis - m) / comb.fac((cntNotVis - m) / 2);
    }
    std::cout << ans << '\n';
}
  • 動態規劃

​ 賽時也有往這個方向考慮,但是不知道怎麼轉移。

​ 移動基本上有兩種型別:

  1. 在某個 \((i, i)\) 位置放置車:這將減少 \(1\) 的空閒行列數。
  2. 將車置於 \((i, j)\) ,其中 \(i \neq j\) :現在計算機也會這樣做,將車置於 \((j, i)\) 處,擋住 \(i\)\(j\) 行以及 \(i\)\(j\) 列。因此空閒行列數減少了 \(2\)

​ 首先,我們算出之前下過的 \(k\) 步,並計算剩餘可放置車的空閒列/行的數量,稱之為 \(m\)

​ 注意移行/列的順序並不影響車的最終配置,因此只有行數才是決定最終配置數的關鍵。

​ 定義 \(dp[i]\) 表示當剩下 \(i\) 行和列時的最終配置數。

​ 由於移除行/列的順序並不重要,我們從移除最後一行或最後一列開始。

​ 在移除 \(i \times i\) 網格中的最後一行或最後一列時,我們有兩種選擇:

  • 我們放置車 \((i, i)\) ,結果是隻刪除最後一行和一列,留下一個 \((i-1) \times (i-1)\) 格。這種情況下的最終配置數為 \(dp[i-1]\)
  • 或者,我們也可以在 \((i, j)\)\((j, i)\) 中的任意 \(j \in \{1, 2, \ldots, i-1\}\) 放置車。這步棋之後, \(j\) th和 \(i\) th行列都被刪除,剩下一個 \((i-2) \times (i-2)\) 格。這就為 \(dp[i]\) 貢獻了 \(2 (i-1) \cdot dp[i-2]\)

總的來說,我們計算了所有 \(i \in \{2, 3, \ldots, n\}\)\(dp[i] = dp[i-1] + 2 (i-1) \cdot dp[i-2]\) ,基本情況為 \(dp[0] = dp[1] = 1\)

我們的最終答案是 \(dp[m]\)

while (t--) {
    int n, k;
    cin >> n >> k;
    int used = 0;
    for (int i = 0; i < k; i++) {
        int r, c;
        cin >> r >> c;
        used += 2 - (r == c);
    }
    int m = n - used;
    dp[0] = dp[1] = 1;
    for (int i = 2; i <= m; i++)
        dp[i] = (dp[i - 1] + 2ll * (i - 1) * dp[i - 2] % MOD) % MOD;
    cout << dp[m] << "\n";
}

D. A BIT of an Inequality

Problem - D - Codeforces

二進位制拆位字首和,只是維護的是到當前這位的 \(1\) 的個數為奇數的個數。

維護出來之後對每個 \(y\) 找最高位的 \(1\),顯然 \(f(x,z)\)\(f(y,z)\) 的這一位的 \(1\) 的個數之和只要為偶數,那麼 \(f(x, y)\oplus f(y, z) > f(x, z)\) 因為此時 \(f(x, z)\) 這一位為 \(0\) 了,而 \(f(x, y)\oplus f(y,z)\) 這一位仍為 \(1\)

顯然為偶數的情況要麼是奇數加奇數,要麼是偶數加偶數

void solve()
{
#define tests
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (auto& x : a)
        std::cin >> x;
    std::vector dp(31, std::vector<int>(n + 1)); // 拆位字首和
    for (int i = 0; i < 31; i++) {
        int sum {};
        for (int j = 0; j < n; j++) {
            sum = (sum + (a[j] >> i & 1)) & 1; // 統計1的個數是不是奇數
            if (sum == 1) {
                dp[i][j + 1] = dp[i][j] + 1;
            } else {
                dp[i][j + 1] = dp[i][j];
            }
        }
    }
    i64 res {};
    for (int i = 0; i < n; i++) {
        int p {};
        for (int j = 30; j >= 0; j--) { // 找這個數最大為1的位
            if (a[i] >> j & 1) {
                p = j;
                break;
            }
        }
        // 奇數 + 奇數的方案
        i64 add1(1LL * dp[p][i] * (dp[p][n] - dp[p][i]));
        i64 add2(1LL * (i + 1 - dp[p][i]) * (n - i - (dp[p][n] - dp[p][i])));
        // 偶數 + 偶數的方案

        res += add1 + add2;
    }
    std::cout << res << '\n';
}

相關文章