這場還挺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';
}
- 動態規劃
賽時也有往這個方向考慮,但是不知道怎麼轉移。
移動基本上有兩種型別:
- 在某個 \((i, i)\) 位置放置車:這將減少 \(1\) 的空閒行列數。
- 將車置於 \((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';
}