1. 程式人生 > 其它 >省選日記 Day-14 - Day-10

省選日記 Day-14 - Day-10

省選日記 Day \(-14\) - Day \(-10\)

Day \(-14\) Mar 20, 2022, Sunday

CSP-S 2021 迴文

場上寫的 \(O(2^n)\), 拿了 \(40\) 分. 同學加了個很強的剪枝可以拿 \(48\).

容易發現, 雙端佇列彈出序列中的一個字尾, 一定也是被這個雙端佇列中的一個子序列彈出得到的.

需要生成的迴文串的前半部分和後半部分一定都是 \(n\) 的排列, 因此它的後半部分一定是由一個 \(a\) 的排列子串彈出得到的.

我們雙指標掃描整個 \(a\) 可以 \(O(n)\) 找出所有是 \(n\) 的排列的子串. 這個子串左邊的字首和右邊的字尾彈出形成了序列的後半段. 接下來我們把這個字首看成是一個從右往左彈出的棧, 字尾看成是從左往右彈出的棧, 中間仍然是雙端佇列. 我們想要判斷的是兩個棧彈出的序列和中間雙端佇列彈出序列相同的可行性.

將中間的雙端佇列分成兩個子序列, 元素分別和兩個棧的元素相同. 這樣只要兩個棧分別和兩個雙端佇列彈出序列相同即可. 判斷一個棧和一個雙端佇列彈出序列是否可以相同, 需要保證每個時刻棧頂元素在佇列端點上. 因為棧的彈出方式唯一, 所以我們可以模擬這個過程 \(O(n)\) 判斷.

如果一個子序列可行, 我們應該如何構造字典序最小的方案呢. 最先彈出的一定是兩個棧的棧底之一. 在確定了起點之後, 我們就可以以左端優先的原則, 在兩端彈出 \(b\) 的前半段內容的過程中, 從中間往外彈出 \(b\) 後半段的內容. 因此以左邊優先的原則, 同時在 \(b\) 的兩端往中間生成即可, 不用構造出具體的 \(b\)

, 過程中只要維護 \(4\) 個指標進行移動即可.

這就是 \(O(n^2)\) 的做法了, 可以拿到 \(60'\) 的好成績.

我們發現彈出的第一個元素只有兩種可能, \(a_1\)\(a_{2n}\), 因此無需討論中間子區間的界限, 只要按 \(a_1\) 開始構造一次, 如果不能構造則以 \(a_{2n}\) 開始構造一次. 如果都不能構造就無解, 否則直接輸出構造就結果即可.

unsigned a[1000005];
char b[1000005], Flg(0);
unsigned m, n, nn, A, B, C, D, t;
unsigned Cnt(0), Ans(0), Tmp(0);
inline void Clr() {
  nn = ((n = RD()) << 1);
  A = 1, B = nn, Flg = 0;
}
signed main() {
  t = RD();
  for (unsigned T(1); T <= t; ++T){
    Clr();
    for (unsigned i(1); i <= nn; ++i) {a[i] = RD();if(a[i] == a[1]) C = D = i;}
    b[1] = b[nn] = 'L', ++A, --C, ++D;
    for (unsigned i(2), j(nn - 1); i <= n; ++i, --j) {
      if((A < C) && (a[A] == a[C])) {b[i] = b[j] = 'L', ++A, --C; continue;}
      if((D <= B) && (a[A] == a[D])) {b[i] = 'L', b[j] = 'R', ++A, ++D; continue;}
      if((A <= C) && (a[B] == a[C])) {b[i] = 'R', b[j] = 'L', --B, --C; continue;}
      if((D < B) && (a[B] == a[D])) {b[i] = b[j] = 'R', --B, ++D; continue;}
      Flg = 1; break;
    }
    if(!Flg) {for (unsigned i(1); i <= nn; ++i) putchar(b[i]); putchar(0x0A); continue;}
    for (unsigned i(1); i < nn; ++i) if(a[i] == a[nn]) {C = D = i; break;}
    Flg = 0, b[1] = 'R', b[nn] = 'L', --C, ++D, A = 1, B = nn - 1;
    for (unsigned i(2), j(nn - 1); i <= n; ++i, --j) {
      if((A < C) && (a[A] == a[C])) {b[i] = b[j] = 'L', ++A, --C; continue;}
      if((D <= B) && (a[A] == a[D])) {b[i] = 'L', b[j] = 'R', ++A, ++D; continue;}
      if((A <= C) && (a[B] == a[C])) {b[i] = 'R', b[j] = 'L', --B, --C; continue;}
      if((D < B) && (a[B] == a[D])) {b[i] = b[j] = 'R', --B, ++D; continue;}
      Flg = 1; break;
    }
    if(!Flg) {for (unsigned i(1); i <= nn; ++i) putchar(b[i]); putchar(0x0A); continue;}
    printf("-1\n");
  }
  return Wild_Donkey;
}

Day \(-13\) Mar 21, 2022, Monday

NOIP2021 方差

容易發現, 每次操作相當於將差分陣列 \(b\) 的兩位交換了, 問題轉化為重新排列 \(b\) 使得 \(b\) 的字首和陣列 \(a\) 的方差最小. 輸出方差的 \(n^2\) 倍, 即為:

\[\begin{aligned} Ans =&n^2 \frac 1n \sum_{i = 1}^n (a_i - \overline {a})^2\\ =&n^2 \frac 1n \sum_{i = 1}^n ({a_i}^2 - 2a_i\overline {a} + \overline {a}^2)\\ =&n^2 \frac 1n (\sum_{i = 1}^n {a_i}^2 - \sum_{i = 1}^n 2a_i\overline {a} + \sum_{i = 1}^n \overline {a}^2)\\ =&n\sum_{i = 1}^n {a_i}^2 - n\sum_{i = 1}^n 2a_i\overline {a} + n\sum_{i = 1}^n \overline {a}^2\\ \end{aligned} \]

接下來用 \(Sum\) 表示序列的總和, 可以得到:

\[\begin{aligned} Sum =& \sum_{i = 1}^n\\ \overline {a} =& \frac{Sum}n\\ \end{aligned} \]

繼續推式子.

\[\begin{aligned} Ans =&n^2 \frac 1n \sum_{i = 1}^n (a_i - \overline {a})^2\\ =&n\sum_{i = 1}^n {a_i}^2 - n\sum_{i = 1}^n 2a_i\frac{Sum}n + n\sum_{i = 1}^n \bigg(\frac{Sum}n\bigg)^2\\ =&n\sum_{i = 1}^n {a_i}^2 - 2Sum^2 + Sum^2\\ =&n\sum_{i = 1}^n {a_i}^2 - Sum^2\\ \end{aligned} \]

我們知道 \(a\)\(b\) 的字首和, 因此 \(a_i = \displaystyle{\sum_{j = 1}^i} b_j\)

\[\begin{aligned} Sum =& \sum_{i = 1}^n \sum_{j = 1}^i b_j\\ =& \sum_{i = 1}^n b_i (n - i + 1)\\ Ans =& n\sum_{i = 1}^n {a_i}^2 - Sum^2\\ = &n\sum_{i = 1}^n \bigg(\sum_{j = 1}^i b_j\bigg)^2 - Sum^2\\ = &n\sum_{i = 1}^n \sum_{j = 1}^i b_j \sum_{k = 1}^i b_k - Sum^2\\ = &n\sum_{j = 1}^n b_j \sum_{k = 1}^n b_k (n - \max(j, k) + 1) - Sum^2\\ = &n\sum_{j = 1}^n \sum_{k = 1}^n b_jb_k(n - \max(j, k) + 1) - \bigg(\sum_{i = 1}^n b_i (n - i + 1)\bigg)^2\\ = &n\sum_{j = 1}^n \sum_{k = 1}^n b_jb_k(n - \max(j, k) + 1) - \sum_{i = 1}^n\sum_{j = 1}^n b_ib_j (n^2 + n(2 - j - i) + ij - i - j + 1)\\ = &\sum_{j = 1}^n \sum_{i = 1}^n b_jb_i(n(n - \max(j, i) + 1) - (n^2 + n(2 - j - i) + ij - i - j + 1))\\ = &\sum_{j = 1}^n \sum_{i = 1}^n b_jb_i(n^2 - n(\max(j, i) - 1) - (n^2 + n(2 - j - i) + ij - i - j + 1))\\ = &\sum_{j = 1}^n \sum_{i = 1}^n b_jb_i(n(j + i - 1 - \max(j, i)) - (ij - i - j + 1))\\ = &\sum_{j = 1}^n \sum_{i = 1}^n b_jb_i(n(\min(j, i) - 1) - ij + i + j - 1)\\ \end{aligned} \]

這樣就找到了每一對 \(b\) 中元素在答案中的貢獻, 我們想辦法去掉 \(\min\) 然後繼續分析.

\[\begin{aligned} Ans = &2\sum_{i = 1}^n \sum_{j = 1}^i b_ib_j(n(j - 1) - ij + i + j - 1) - \sum_{i = 1}^n {b_i}^2(n(i - 1) - i^2 + 2i - 1)\\ = &2\sum_{i = 1}^n \sum_{j = 1}^i b_ib_j(j - 1)(n - i + 1) - \sum_{i = 1}^n {b_i}^2(i - 1)(n - i + 1)\\ = &2\sum_{i = 1}^n \sum_{j = 1}^{i - 1} b_ib_j(j - 1)(n - i + 1) + \sum_{i = 1}^n {b_i}^2(i - 1)(n - i + 1)\\ = &2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i - 1} b_{i + 1}b_{j + 1}j(n - i) + \sum_{i = 1}^{n - 1} {b_{i + 1}}^2i(n - i)\\ = &2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i - 1} b_{i + 1}b_{j + 1}j(n - i) + n\sum_{i = 1}^{n - 1} {b_{i + 1}}^2i - \sum_{i = 1}^{n - 1} {b_{i + 1}}^2i^2\\ \end{aligned} \]

發現 \(b_1\) 對答案無影響.

其實這樣就可以去寫爆搜了, 在搜尋過程中維護答案, 複雜度 \(O((n - 1)!)\). 拿到 \(20'\).

如果這時輸出方案中 \(a\) 的序列, 就會發現方案中的差分陣列是單谷的. 然後就可以把爆搜優化到 \(O(2^{n - 1})\). 但是為什麼是單谷呢? 我們無從得知.

對於 \(i \in [1, n - 2]\), 假設有 \(x = b_{i + 1}\), \(y = b_{i + 2}\). 則如果交換它們的值, 會使得:

\(k < i\)\(yb_{k + 1}\) 的貢獻乘 \(\dfrac {n - i}{n - i - 1}\), 增加了 \(\dfrac {2yb_{k + 1}k}{n - i - 1}\)

\(k < i\)\(xb_{k + 1}\) 的貢獻乘 \(\dfrac{n - i - 1}{n - i}\), 減少了 \(\dfrac {2xb_{k + 1}k}{n - i}\)

\(k < i\)\(b_{k + 1}\) 的貢獻一共增加 \(2b_{k + 1}k(\dfrac y{n - i - 1} - \dfrac x{n - i})\)

\(k > i + 1\)\(yb_{k + 1}\) 的貢獻乘 \(\dfrac {i}{i + 1}\), 減少了 \(\dfrac {2yb_{k + 1}(n - k)}{i + 1}\)

\(k > i + 1\)\(xb_{k + 1}\) 的貢獻乘 \(\dfrac {i + 1}{i}\), 增加了 \(\dfrac {2xb_{k + 1}(n - k)}{i}\)

\(k > i + 1\)\(b_{k + 1}\) 的貢獻一共增加 \(2b_{k + 1}(n - k)(\dfrac x{i} - \dfrac y{i + 1})\)

\(xy\) 的貢獻沒有變化.

\(x^2\) 的貢獻乘 \(\dfrac{(i + 1)(n - i - 1)}{i(n - i)}\), 增加 \(\dfrac {x^2((i + 1)(n - i - 1) - i(n - i))}{i(n - i)} = \dfrac {x^2(n - 1 - 2i)}{i(n - i)}\).

\(y^2\) 的貢獻乘 \(\dfrac{i(n - i)}{(i + 1)(n - i - 1)}\), 增加 \(\dfrac {y^2(i(n - i) - (i + 1)(n - i - 1))}{(i + 1)(n - i - 1)} = \dfrac {y^2(1 + 2i - n)}{(i + 1)(n - i - 1)}\).

事到如今, 這個式子已經過於複雜, 所以重新審視一開始的式子.

\[Ans = n\sum_{i = 1}^n {a_i}^2 - Sum^2 \]

發現 \(Sum\)\(b\) 的排列方案無關, 是個定值. \(b_1\) 我們也動不了. 而且 \(b_1\) 和方差也沒關係.

因此我們只要讓 \(Tmp = \displaystyle \sum_{i = 1}^{n - 1} ({a_{i + 1} - a_1})^2\) 最小即可.

\[\begin{aligned} Tmp &= \sum_{i = 1}^{n - 1} ({a_{i + 1} - a_1})^2\\ &= \sum_{i = 1}^{n - 1} (\sum_{j = 1}^{i} b_{j + 1})^2\\ &= \sum_{i = 1}^{n - 1} (\sum_{j = 1}^{i}\sum_{k = 1}^i b_{j + 1}b_{k + 1})^2\\ &= \sum_{i = 1}^{n - 1} \sum_{j = 1}^{n - 1} b_{i + 1}b_{j + 1}(n - \max(i, j))\\ &= 2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i - 1} b_{i + 1}b_{j + 1}(n - i) + \sum_{i = 1}^{n - 1} {b_{i + 1}}^2(n - i)\\ \end{aligned} \]

對於 \(i \in [1, n - 2]\), 假設有 \(x = b_{i + 1}\), \(y = b_{i + 2}\). 則如果交換它們的值, 會使得:

\(k < i\)\(yb_{k + 1}\) 的貢獻乘 \(\dfrac {n - i}{n - i - 1}\), 增加了 \(\dfrac {2yb_{k + 1}}{n - i - 1}\)

\(k < i\)\(xb_{k + 1}\) 的貢獻乘 \(\dfrac{n - i - 1}{n - i}\), 減少了 \(\dfrac {2xb_{k + 1}}{n - i}\)

\(k < i\)\(b_{k + 1}\) 的貢獻一共增加 \(2(\dfrac y{n - i - 1} - \dfrac x{n - i})\displaystyle \sum_{k = 1}^{i - 1}b_{k + 1}\)

\(k > i + 1\)\(b_{k + 1}\) 的貢獻沒有變化.

\(xy\) 的貢獻沒有變化.

\(x^2\) 的貢獻乘 \(\dfrac{n - i - 1}{n - i}\), 減少 \(\dfrac{x^2}{n - i}\).

\(y^2\) 的貢獻乘 \(\dfrac{n - i}{n - i - 1}\), 增加 \(\dfrac {y^2}{n - i - 1}\).

\(Tmp\) 增加 \(\dfrac {y^2}{n - i - 1} - \dfrac{x^2}{n - i} + 2(\dfrac y{n - i - 1} - \dfrac x{n - i})\displaystyle \sum_{k = 1}^{i - 1}b_{k + 1} = \dfrac{y^2(n - i) - x^2(n - i - 1) + 2(y(n - i) - x(n - i - 1))\displaystyle \sum_{k = 1}^{i - 1}b_{k + 1}}{(n - i - 1)(n - i)}\).

這個值是關於 \(x\) 的二次函式和關於 \(y\) 的二次函式相減. \(y\) 所在的函式要更陡, 且它們的最右邊交點是 \((0, 0)\), 因此對於 \(x < y\) 的情況交換一定會使得 \(Tmp\) 增加. 所以可以知道最優方案差分陣列一定是遞增的.

不是說結論是單谷嗎, 怎麼成遞增了? 用屁股想想都知道, 要是隻需要讓 \(a\) 的平方和最小, 那差分陣列必然只能遞增啊.

所以真相只能有一個, 就是我們的證明炸了. 還記得我們的推導是建立在 \(b\) 的排列不影響 \(Sum\) 的基礎上的嗎? 但是事情沒有這麼簡單, 因為 \(Sum\)\(b\) 是有關的.

重新審視我們之前的式子.

\[\begin{aligned} Ans = &2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i - 1} b_{i + 1}b_{j + 1}j(n - i) + \sum_{i = 1}^{n - 1} {b_{i + 1}}^2i(n - i)\\ \end{aligned} \]

這時如果 \(b_{i + 1}\) 增加 \(x\), \(Ans\) 會增加:

\[\begin{aligned} &2\sum_{j = 1}^{i - 1} xb_{j + 1}j(n - i) + 2\sum_{j = i + 1}^{n - 1} xb_{j + 1}i(n - j) + (x^2 + 2xb_{i + 1})i(n - i)\\ = &2x\bigg(\sum_{j = 1}^{i - 1} b_{j + 1}j(n - i) + \sum_{j = i + 1}^{n - 1} b_{j + 1}i(n - j)\bigg) + (x^2 + 2xb_{i + 1})i(n - i)\\ = &2x\bigg(\sum_{j = 1}^{i} b_{j + 1}j(n - i) + \sum_{j = i}^{n - 1} b_{j + 1}i(n - j)\bigg) + x^2i(n - i)\\ \end{aligned} \]

容易發現...

容易發現個 \(\huge{J8}\).

老子不證了. 奶奶滴. 這個題就是單谷, 怎麼支吧, 你看 \(i(n - i)\) 不就是越往中間越大嗎, 所以我們把小的放在中間一看就很小啊. 焯!

我再試圖證明這種打表可知的東西, 我就是大傻逼.

接下來考慮滿足條件的情況下, 如何找出最小答案. 容易發現答案方案中的差分陣列內, 最小的元素一定組成一個子區間. 所以我們只要以最小的 \(i\) 個為一個子問題, DP 解決即可.

不過問題又出現了, 我們如何保證 DP 正確呢? 或者說, 憑什麼 \(i + 1\) 狀態的最優方案中, 前 \(i\) 小的數字就必須排成 \(i\) 狀態中的順序而不是其它什麼順序呢? 所以接下來讓我們來證明這個問題的區域性最優性.

仍然審視之前的式子:

\[\begin{aligned} Ans = &2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i - 1} b_{i + 1}b_{j + 1}j(n - i) + \sum_{i = 1}^{n - 1} {b_{i + 1}}^2i(n - i)\\ \end{aligned} \]

我們假設增加一個點 \(b_{n + 1}\), 那麼答案會增加:

\[\begin{aligned} &2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i - 1} b_{i + 1}b_{j + 1}j + \sum_{i = 1}^{n - 1} {b_{i + 1}}^2i + 2\sum_{i = 1}^{n - 1} b_{n + 1}b_{i + 1}i + {b_{n + 1}}^2n\\ &2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i - 1} b_{i + 1}b_{j + 1}j + \sum_{i = 1}^{n - 1} {b_{i + 1}}^2i + 2\sum_{i = 1}^n b_{n + 1}b_{i + 1}i - {b_{n + 1}}^2n\\ &2\sum_{i = 1}^{n - 1} \sum_{j = 1}^{i} b_{i + 1}b_{j + 1}j + 2b_{n + 1}\sum_{i = 1}^n b_{i + 1}i - {b_{n + 1}}^2n\\ &2\sum_{i = 1}^n \sum_{j = 1}^i b_{i + 1}b_{j + 1}j - {b_{n + 1}}^2n\\ \end{aligned} \]

然後發現這個方法還 DP 個屁, 直接寄了.

高階的食材往往只需要最樸素的烹飪方式. 俗話說的好, 打不過, 就擺爛. 如果不能確定區域性最優性, 列舉所有情況不就好了嗎.

\[Ans = n\sum_{i = 1}^n {a_i}^2 - Sum^2\\ \]

以前 \(i\) 小的差分值為階段設計 DP. 新的差分值只可能插入到左端或右端.

如果我們此時在後面加一個 \(b_{n + 1}\), 相當於增加一個 \(a_{n + 1} = a_n + b_{n + 1}\). 平方和增加 \({a_{n + 1}}^2\), \(Sum\) 增加 \(a_{n + 1}\).

如果從前面加一個 \(b_0\), 相當於在 \(a_1\) 前面插入 \(a_0\) 並且所有 \(a\) 值增加 \(b_0\). 平方和增加 \({b_0}^2 + \displaystyle \sum_{i = 1}^n({b_0}^2 + 2a_ib_0) = (n + 1){b_0}^2 + 2b_0Sum\), \(Sum\) 增加 \(b_0(n + 1)\).

答案只和平方和和 \(Sum\) 有關. 因此我們記錄 \(Sum\) 作為狀態是足夠的.

我們發現 \(n\) 不變時 \(a_{n + 1}\) 是個定值, 因此 \(Sum' = Sum + a_{n + 1}\) 和平方和都是可以 \(O(1)\) 得到的. 我們用 \(f_{i, j}\) 表示由前 \(i\) 小的 \(b\) 值生成的序列總和是 \(j\) 時的最小平方和. 可以實現 \(O(1)\) 轉移:

\[f_{i, Sum} = \min(f_{i - 1, Sum - Sumb_i} + {Sumb_i}^2, f_{i - 1, Sum - ib_i} + i{b_i}^2 + 2b_i(Sum - ib_i)) \]

我們知道 \(j\) 的取值範圍是 \(O(nMaxa)\) 的, 階段是 \(O(n)\), 轉移 \(O(1)\), 因此直接 DP 的複雜度是 \(O(n^2Maxa)\). 這樣大概率可以拿到 \(72'\), 甚至有希望拿到 \(88'\).

由於這個狀態較為稀疏, 因此貌似這個做法可以順利拿到 \(88'\). 現在就剩下最後三個點了, 這三個點 \(n\) 極大, \(Maxa\) 極小.

前面說過, \(b_1\) 對答案沒有任何貢獻, 所以我們姑且認為它是 \(0\).

然後發現在 \(n\) 較大的時候, \(b\) 有相當一部分是 \(0\), 這一部分可以直接忽略, 因為它們的平方和必為 \(0\). 我們最多有 \(Maxa\) 個非零的差分值, 因此階段數就變成了 \(min(n, Maxa)\). 所以至少現在的複雜度是 \(O(nMaxa\min(n, Maxa))\).

這樣可以順利通過 \(23 \sim 25\) 號點了.

unsigned long long f[1000005];
unsigned a[10005], b[10005], m(0), n;
unsigned long long Ans(0x3f3f3f3f3f3f3f3f);
unsigned A, B, C, D, t;
unsigned Cnt(0), Tmp(0);
signed main() {
  n = RD() - 1, A = RD();
  for (unsigned i(1); i <= n; ++i) b[i] = (B = RD()) - A, A = B;
  sort(b + 1, b + n + 1);
  memset(f, 0x3f, sizeof(f)), f[0] = 0;
  for (unsigned i(1); i <= n; ++i) a[i] = a[i - 1] + b[i];
  for (unsigned i(1); i <= n; ++i) {
    Tmp = m + max(i * b[i], a[i]);
    unsigned long long g[Tmp + 1];
    memset(g, 0x3f, (Tmp + 1) << 3);
    unsigned aa(a[i] * a[i]), bb(b[i] * b[i]);
    for (unsigned j(0); j <= m; ++j) {
      unsigned long long &To(g[j + a[i]]), &To1(g[j + i * b[i]]);
      To = min(To, f[j] + aa);
      To1 = min(To1, f[j] + (unsigned long long)i * bb + ((j * b[i]) << 1));
    }
    memcpy(f, g, (Tmp + 1) << 3), m = Tmp;
  }
  for (unsigned i(0); i <= m; ++i) if(f[i] < 0x3f3f3f3f3f3f3f3f) Ans = min(Ans, (n + 1) * f[i] - (unsigned long long)i * i);
  printf("%llu\n", Ans);
  return Wild_Donkey;
}

題解裡竟然真的有老哥寫出證明來了. 很簡單, 在平均數之上的, 我們需要 \(a\) 儘可能靠近 \(\overline{a}\), 所以這時差分陣列從左到右遞增, 在平均數之下, 我們需要 \(a\) 儘可能靠近 \(\overline{a}\), 所以這時差分陣列從右到左遞增.

Day \(-12\) Mar 22, 2022, Tuesday

快速莫比烏斯/沃爾什變換 (FMT/FWT)

求或卷積和與卷積都很好算, 算一下高維字首和就好了.

但是異或卷積有點困難.

我們考慮類似於 FFT 那樣分別考慮 \(f \times g\) 的奇偶項的貢獻.

\(f\) 偶數項提取出來作為 \(f_0\), 偶數項提取出來作為 \(f_1\), \(g\)\(f \times g\) 同理. 容易發現, 偶數是由奇偶性相同的數字異或得到的, 因此 \((f \times g)_0 = f_0 \times g_0 + f_1 \times g_1\); 奇數是由奇偶性不同的數字異或得到的, 因此 \((g \times f)_1 = g_0 \times f_1 + g_1 \times f_0\).

如果是這樣遞迴計算的話, 複雜度 \(T(n) = n + 4T(\frac n2)\), 仍然是 \(O(n^2)\).

考慮多項式 \(a = f_0 + f_1\), \(b = f_0 - f_1\), \(c = g_0 + g_1\), \(d = g_0 - g_1\).

\[\begin{aligned} a \times c &= f_0 \times g_0 + f_0 \times g_1 + f_1 \times g_0 + f_1 \times g_1\\ b \times d &= f_0 \times g_0 - f_0 \times g_1 - f_1 \times g_0 + f_1 \times g_1\\ \frac {a \times c + b \times d}2 &= f_0 \times g_0 + f_1 \times g_1 = (f \times g)_0\\ \frac {a \times c - b \times d}2 &= f_0 \times g_1 + f_1 \times g_0 = (f \times g)_1\\ \end{aligned} \]

如果我們對 \(a\), \(b\), \(c\), \(d\) 繼續分下去, 直到長度為 \(1\) 的時候直接點乘, 最後再合併起來, 時間複雜度 \(T(n) = n + 2T(\frac n2)\), 也就是 \(O(n\log n)\).

我們發現遞迴的最後一層, \(f\), \(g\) 各分成了 \(n\) 個長度為 \(1\) 的多項式, 我們把 \(f\)\(n\) 個長度為 \(1\) 的多項式按從 \(a\)\(b\) 的順序遞迴的 DFS 樹上中序遍歷的順序拼成一個多項式 \(F\). (這個順序可以認為是 \(F\) 的前 \(\frac n2\) 項是 \(a\) 生成的多項式拼成的, 後 \(\frac n2\) 項是 \(b\) 生成的多項式拼成的, 以此類推). 同樣 \(G\) 也是由 \(g\) 生成的 \(n\) 個長度為 \(1\) 的多項式拼成的.

在遞迴底層做的就是把這些長度為 \(1\) 的多項式乘起來.

回溯時相當於是用 \(n\) 個長度為 \(1\) 的多項式合成一個長度為 \(n\) 的多項式. 回溯時已知 \(a \times c\), \(b \times d\) 兩個多項式, 用它們加減得到 \(f \times g\) 的奇數位和偶數位.

生成 \(F\), \(G\) 的過程便是快速沃爾什變換 (FWT): \(F = \text{FWT}(f)\), \(G = \text{FWT}(g)\). 而通過 \(F \cdot G\) 合成 \(f \times g\) 的過程便是 FWT 的逆變換, IFWT \(f = \text{IFWT}(F)\).

解決異或卷積問題的多項式運算, 定義為: 長度為 \(1\) 的時候 \(F = f\), 其他情況下滿足:

\[F_0 = \text{FWT}(f_0) + \text{FWT}(f_1)\\ F_1 = \text{FWT}(f_0) - \text{FWT}(f_1) \]

點乘後的多項式 \(F \cdot G\) 也就是 \(FWT(f \times g)\). 最後逆變換回去即可.

逆變換 \(f = \text{IFWT}(F)\) 也很簡單.

逆變換也很簡單:

\[\begin{aligned} \frac{F_0 + F_1}2 &= \text{FWT}(f_0)\\ \text{IFWT}(\frac{F_0 + F_1}2) &= f_0\\ \frac{F_0 - F_1}2 &= \text{FWT}(f_1)\\ \text{IFWT}(\frac{F_0 - F_1}2) &= f_1\\ \end{aligned} \]

其實我們可以把 \(\frac 12\) 提出來, 使得 \(nf = \text{IFWT}(F)\) 整理出來就是:

\[\begin{aligned} F_0 &= \text{FWT}(f_0) + \text{FWT}(f_1)\\ F_1 &= \text{FWT}(f_0) - \text{FWT}(f_1)\\ n f_0 &= \text{IFWT}(F_0 + F_1)\\ n f_1 &= \text{IFWT}(F_0 - F_1) \end{aligned} \]

對於或卷積和與卷積, 雖然可以認為是高維字首和 (字尾和), 但是為了實現 FWT 大一統計劃, 也可以進行同樣的操作:

\[\begin{aligned} (f \times g)_0 &= f_0 \times g_0\\ (f \times g)_1 &= f_0 \times g_1 + f_1 \times g_0 + f_1 \times g_1\\ \end{aligned} \]

這個是或卷積的操作:

\[\begin{aligned} a &= f_0\\ b &= f_0 + f_1\\ c &= g_0\\ b &= g_0 + g_1\\ (f \times g)_0 &= a \times b\\ (f \times g)_1 &= c \times d - a \times b\\ \end{aligned} \]

解決或卷積的 FWT 和 IFWT 是這樣的:

\[\begin{aligned} F_0 &= \text{FWT}(f_0)\\ F_1 &= \text{FWT}(f_0) + \text{FWT}(f_1)\\ f_0 &= \text{IFWT}(F_0)\\ f_1 &= \text{IFWT}(F_1 - F_0) \end{aligned} \]

最後是與卷積:

\[\begin{aligned} (f \times g)_0 &= f_0 \times g_0 + f_0 \times g_1 + f_1 \times g_0\\ (f \times g)_1 &= f_1 \times g_1\\ \end{aligned} \] \[\begin{aligned} a &= f_1\\ b &= f_0 + f_1\\ c &= g_1\\ b &= g_0 + g_1\\ (f \times g)_0 &= a \times b - c \times d\\ (f \times g)_1 &= c \times d\\ \end{aligned} \]

接下來是變換的式子:

\[\begin{aligned} F_0 &= \text{FWT}(f_0) + \text{FWT}(f_1)\\ F_1 &= \text{FWT}(f_1)\\ f_0 &= \text{IFWT}(F_0 - F_1)\\ f_1 &= \text{IFWT}(F_1)\\ \end{aligned} \]

然後我們就可以進行 DIF, DIT 計算了.

const unsigned Mod(998244353);
unsigned a[132005], b[132005], m, n;
unsigned long long Iv;
unsigned A, B, C, D, t;
unsigned Cnt(0), Ans(0), Tmp(0);
char Type(0);
inline void Mn(unsigned& x) {x -= ((x >= Mod) ? Mod : 0);}
inline unsigned long long Inv(unsigned long long x) {
  unsigned long long Rt(1);
  unsigned y(998244351);
  while (y) { if(y & 1) Rt = Rt * x % Mod; x = x * x % Mod, y >>= 1; }
  return Rt;
}
inline void DIF (unsigned *f) {
  for (unsigned i(n >> 1); i; i >>= 1) {
    for (unsigned j(0); j < n; ++j) if(!(j & i)) {
      unsigned Tma(f[j]), Tmb(f[j ^ i]);
      if(Type == 1) { Mn(f[j ^ i] += Tma); continue;}
      if(Type == 2) { Mn(f[j] += Tmb); continue;}
      Mn(f[j] += Tmb);
      Mn(f[j ^ i] = Mod + Tma - Tmb);
    }
  }
}
inline void DIT (unsigned *f) {
  for (unsigned i(1); i < n; i <<= 1) {
    for (unsigned j(0); j < n; ++j) if(!(j & i)) {
      unsigned Tma(f[j]), Tmb(f[j ^ i]);
      if(Type == 1) { Mn(f[j ^ i] += Mod - Tma); continue;}
      if(Type == 2) { Mn(f[j] += Mod - Tmb); continue;}
      Mn(f[j] += Tmb);
      Mn(f[j ^ i] = Mod + Tma - Tmb);
    }
  }
}
inline void Calc () {
  unsigned Ta[n], Tb[n];
  memcpy(Ta, a, n << 2);
  memcpy(Tb, b, n << 2);
  DIF(Ta), DIF(Tb);
  for (unsigned i(0); i < n; ++i) Ta[i] = (unsigned long long)Ta[i] * Tb[i] % Mod;
  DIT(Ta);
  if(Type == 3) for (unsigned i(0); i < n; ++i) Ta[i] = Ta[i] * Iv % Mod;
  for (unsigned i(0); i < n; ++i) printf("%u ", Ta[i]); putchar(0x0A);
}
signed main() {
  Iv = Inv(n = (1 << (m = RD())));
  for (unsigned i(0); i < n; ++i) a[i] = RD();
  for (unsigned i(0); i < n; ++i) b[i] = RD();
  Type = 1, Calc();
  Type = 2, Calc();
  Type = 3, Calc();
  return Wild_Donkey;
}

CF

掉大分, \(10\) 分鐘才幹出 A, 又是 \(10\) 分鐘幹出 B. 做 B 的時候一度質疑過貪心的正確性, 但是後來才發現能加不加是純純傻逼.

C 我使用了 Manacher, 雖然寫的時候很費勁但是很幸運一遍過了. 哥們告訴我這個題哪用什麼傻逼 Manacher, 直接判斷兩端就完事了, 因為這個題判斷的是字符集大小為 \(2\) 的最短迴文字首.

搞完 C 還剩 50min 了. D 匆匆忙忙讀完題, 沒有意識到一個怪只能選一種兵. 然後就 G 了, 想了半小時也沒有進展. 好在有兄弟提醒我只能選一類, 發現血量和傷害就是在放屁.

我們假設選擇的兵的血是 \(h\), 攻擊力是 \(d\), 打的怪的血是 \(H\), 攻擊力是 \(D\), 那麼我們需要在小於 \(t = \frac hD\) 的時間內幹掉怪, 假設買 \(x\) 個兵, 那麼幹掉怪的時間是 \(\frac H{dx}\), 於是有式子 \(\frac hD > \frac H{dx}\), 也就是 \(hdx > HD\). 設兵的戰鬥力是它的紅藍相乘, \(a = hd\), 怪也是一樣的 \(b = HD\). 這就是為什麼說血量和攻擊力是放屁, 我們只記錄戰鬥力即可.

接下來就是對於一個怪, 找到一種兵, 使得 \(xa > b\)\(xc\) 最小. 發現 \(C\) 很小, 所以處理 \(f_i\) 表示花不超過 \(i\) 塊錢, 可以湊出的最大戰鬥力. 每次插入一個兵, 就更新 \(f_c = max(f_c, a)\). 插入完了掃一遍 \(f\) 陣列, 對於 \(f_i\), 首先 \(f_{i + 1} = max(f_{i + 1}, f_i)\), 然後列舉 \(x\), 使得 \(f_{ix} = max(f_{ix}, xf_i)\). 這個複雜度是調和級數求和, \(O(C \ln C)\).

詢問時只要二分答案即可. 在比賽結束之後 4min 及時 AC 了此題.

這次貌似掉了 \(132\) 分. 再見了我的紫名.

Day \(-11\) Mar 23, 2022, Wednesday

CERC2013 Escape

這個題首先有幾個比較明顯的剪枝: 不加血的葉子是無用的, 統統刪掉, 加血的節點可以和父親合併.

對於一個父親加血的加血節點, 可以把它的兒子接到父親上, 然後把它刪除, 將父親加血量加上它的加血量.

對於一個父親扣血的加血節點, 可以把它的兒子接到父親上, 父親節點開一個變數儲存所有被刪除的加血兒子的加血總量.

假設我們現在在一個子樹的根, 我們最想知道的是如何獲得門檻最低的血量. 每個血包需要經過一些怪物, 我們一定是選擇那些回血比掉血多的血包, 所以我們每個血包可以記錄兩個量, 取得它的門檻和取得它的淨賺.

我們對於每個子樹, 處理一個堆, 裡面按門檻順序儲存了每個血包的淨賺. 把當前血量所有可以吃的血包都吃了, 直到不能吃為止.

我們把從起點到終點路徑上的點單獨拉出來, 變成一條鏈, 鏈上每個點掛著一個子樹, 每個子樹維護一個堆. 我們從起點到終點掃, 路上吃掉所有可吃的血包, 中途不能走了就寄了, 走到終點就可以輸出 escaped.

為了維護堆, 我們使用 map 加上啟發式合併, 對於每個點, 如果它沒有淨賺, 那麼我們強行提高它的門檻以掠奪更多子樹內的血包使它能夠淨賺, 如果把子樹內所有血包都吃了也不能回本, 說明這棵子樹就是個垃圾, 直接刪除即可.

調了一天, 多虧了好兄弟來給我當小黃鴨才調出來.

long long CurC(0);
unsigned m, n, A, B, t;
char No(0);
struct St {
  map <long long, long long> S;
}SC[200005], * CntS(SC);
inline St* Mrg(St* x, St* y) {
  if (x->S.size() < y->S.size()) swap(x, y);
  for (auto i : y->S)
    if (x->S.find(i.first) != x->S.end()) x->S[i.first] += i.second;
    else x->S.insert(i);
  return x;
}
struct Node {
  vector <Node*> E;
  St* My;
  long long Val, Earn;
  char Im;
}N[200005], * Link[200005], * Des;
inline void Clr() {
  while (CntS > SC) CntS->S.clear(), --CntS;
  for (unsigned i(1); i <= n; ++i) N[i].E.clear();
  n = RD(), Des = N + RD(), No = m = CurC = 0;
}
inline char DFS1(Node* x, Node* Fa) {
  x->Earn = 0;
  unsigned Lim(x->E.size());
  for (unsigned i(0); i < Lim; ++i) if ((x->E[i] != Fa) && (!(x->E[i]->Im))) {
    Node* Cur(x->E[i]);
    if (Cur->Val >= 0) {
      if (Cur == Des) Des = x;
      Cur->Im = 1;
      if (x->Val >= 0) x->Val += Cur->Val;
      else x->Earn += Cur->Val;
      for (auto j : Cur->E) if (j != x) x->E.push_back(j), ++Lim;
    }
  }
  vector <Node*> Son;
  for (auto i : x->E) if ((i != Fa) && (!(i->Im))) Son.push_back(i);
  x->E.clear();
  char Flg(0);
  for (auto i : Son) {
    if (DFS1(i, x)) Flg = 1;
    else x->E.push_back(i);
  }
  if (x == Des) Flg = 1;
  if (Flg) Link[++m] = x;
  return Flg;
}
inline void DFS2(Node* x) {
  if(x->Val < 0) x->Val = -(x->Val);
  else x->Earn = x->Val, x->Val = 0;
  x->My = ++CntS;
  for (auto i : x->E) DFS2(i), x->My = Mrg(x->My, i->My);
  long long Need(x->Earn - x->Val);
  for (map<long long, long long>::iterator i(x->My->S.begin()); x->My->S.size(); i = x->My->S.begin()) {
    if ((Need <= 0) || (i->first <= x->Earn)) {
      if (x->Earn >= i->first) x->Earn += i->second;
      else x->Val += i->first - x->Earn, x->Earn += i->first - x->Earn + i->second;
      Need += i->second, x->My->S.erase(i);
    }
    else break;
  }
  if (Need > 0) x->My->S[x->Val] = Need;
  else x->My->S.clear();
}
signed main() {
  t = RD();
  for (unsigned T(1); T <= t; ++T) {
    Clr();
    for (unsigned i(1); i <= n; ++i) N[i].Val = RDsg(), N[i].Im = 0;
    CurC = 0;
    for (unsigned i(1); i < n; ++i) {
      A = RD(), B = RD();
      N[A].E.push_back(N + B);
      N[B].E.push_back(N + A);
    }
    St* All(SC);
    All->S.clear(), DFS1(N + 1, NULL), CurC = 0;
    for (unsigned i(m); i; --i) {
      if (CurC < (-(Link[i]->Val))) { No = 1; break; }
      CurC += Link[i]->Val + Link[i]->Earn, Link[i]->Val = Link[i]->Earn = 0, DFS2(Link[i]);
      All = Mrg(All, Link[i]->My);
      for (map<long long, long long>::iterator j(All->S.begin()); All->S.size(); j = All->S.begin()) {
        if (j->first <= CurC) CurC += j->second, All->S.erase(j);
        else break;
      }
    }  
    printf(No ? "trapped\n" : "escaped\n");
  }
  return Wild_Donkey;
}

PrSl2021 卡牌遊戲

滿足每個數互不相同, 離散化使得所有值分佈在 \([1, 2n]\) 中.

一個容易想到的想法是規定一個最小值 \(x\), 然後在所有數都大於等於這個 \(x\) 並且讓最大值儘可能小.

如果存在 \(a_i < x\), \(b_i < x\) 同時成立的情況, 那麼 \(x\) 是不合法的最小值. 因此 \(x\) 的取值範圍即為 \([1, \min_{i = 1}^n(\max(a_i, b_i))]\).

一個卡牌必須翻轉的條件是 \(a_i < x\).

一個卡牌不能翻轉的條件是 \(b_i < x\).

其餘的卡牌一定滿足 \(a_i \geq x\)\(b_i \geq x\), 那麼我們利用剩餘的翻轉次數, 對於 \(a_i > b_i\) 的卡牌按 \(a_i\) 從大到小的順序將它們進行翻轉.

很容易寫出列舉 \(x\) 然後 \(O(n)\) 進行翻轉的 \(O(n^2)\) 寫法.

我們把卡牌分為兩類, 一類是 \(\min(a_i, b_i) < x\) 的卡牌, 它們的狀態已經定了. 剩下的是按需要翻轉的卡牌. 我們只需要對兩類的卡分別算最大值, 然後取最大的即可.

第二類卡牌按 \(a_i\) 大小分成兩段, \(a_i\) 較小的一段保持 \(a_i\) 朝上, 第二段是將 \(a_i\), \(b_i\) 中較小的一段朝上. 我們可以預處理 \(a\) 的字首最大值, \(\min(a_i, b_i)\) 的字尾最大值. 只要知道分段的分界點 \(End\) 就可以查詢第二類的最大值了. 我們認為 \([1, End)\) 不翻轉, \([End, n]\) 將最小值朝上.

一開始所有點都是第二類, 我們貪心地按 \(a_i\) 從大到小, 將 \(a_i > b_i\) 的卡翻過去即可, 直到次數用完或序列掃完為止. 計仍然可以翻轉的次數為 \(Emp\).

容易發現, \(x\) 每次增加 \(1\), 都會將一個第二類卡牌變成第一類卡牌, 這張卡牌一定需要使 \(a_i\), \(b_i\) 的最大值朝上, 因此我們統計第二類的最大值的時候就算是把已經變成第一類的卡牌統計進去也不會影響答案. 所以仍然是隻要知道 \(End\) 即可求出此時答案.

從小到大列舉 \(x\), 設每次變成第一類的點是 \(y\). 那麼一定有 \(\min(a_y, b_y) = x - 1\), 我們不能讓 \(x - 1\) 朝上, 所以只能是 \(\max(a_y, b_y)\) 朝上, 我們用這個值更新一下第一類點的最大值, 然後對第二類點的最大值進行修正.

如果 \(a_y = x - 1\), 則它應該被翻轉. 如果這時 \(Emp = 0\), 那麼我們將 \(End\) 往右移動, 撤銷一個翻轉. 然後將 \(Emp\) 減少 \(1\), 因為我們用一次翻轉將 \(y\)\(b\) 朝上了.

如果 \(b_y = x - 1\), 則它不能被翻轉. 而且因為 \(x \leq \min_{i = 1}^n(\max(a_i, b_i))\), 所以一定有 \(b_y < a_y\). 如果這時 \(y \geq End\), 說明它已經被翻轉了. 而這種翻轉是不允許的, 所以我們應該把它們翻過來. 由於 \(a_y\) 一定朝上, 所以無論 \(y\) 左邊的點如何掙扎, 都無法使最大值小於 \(a_y\), 所以我們選擇擺爛, 瘋狂撤銷第二類點的翻轉使 \(End > y\).

這樣我們就可以對每個 \(x\), 均攤 \(O(1)\) 地查詢進行操作後的最大值了, 算上離散化就可以 \(O(n \log n)\) 通過此題.

unsigned Li[2000005], a[1000005], b[1000005], At[2000005], Suf[1000005], Pre[1000005], m, n;
unsigned A, B, C, D, t, K, End, Emp;
unsigned Cnt(0), Ans(0x3f3f3f3f), Tmp(0), Top(0x3f3f3f3f);
signed main() {
  m = ((n = RD()) << 1), Emp = K = RD();
  for (unsigned i(1); i <= n; ++i) Li[i] = a[i] = RD();
  for (unsigned i(1); i <= n; ++i) Li[i + n] = b[i] = RD();
  sort(Li + 1, Li + m + 1);
  for (unsigned i(1); i <= n; ++i) a[i] = lower_bound(Li + 1, Li + m + 1, a[i]) - Li;
  for (unsigned i(1); i <= n; ++i) b[i] = lower_bound(Li + 1, Li + m + 1, b[i]) - Li;
  for (unsigned i(1); i <= n; ++i) Top = min(Top, max(a[i], b[i]));
  for (unsigned i(1); i <= n; ++i) At[a[i]] = At[b[i]] = i;
  for (unsigned i(1); i <= n; ++i) Pre[i] = max(Pre[i - 1], a[i]);
  for (unsigned i(n); i; --i) Suf[i] = max(Suf[i + 1], min(a[i], b[i]));
  for (unsigned i(n), j(K); i; --i) {
    if(j) End = i; 
    if((a[i] > b[i]) && j) --j, --Emp, Tmp = max(b[i], Tmp);
    else Tmp = max(a[i], Tmp);
  }
  Ans = Li[max(Pre[End - 1], Suf[End])] - Li[1];
  for (unsigned i(1); i < Top; ++i) {
    unsigned Pos(At[i]);
    if(a[Pos] == i) {
      Tmp = max(Tmp, b[Pos]);
      if(Pos < End) {
        while ((!Emp) && (End <= n)) {if(a[End] > b[End]) ++Emp; ++End;}
        --Emp;
      }
    }
    else {
      Tmp = max(Tmp, a[Pos]);
      while (End <= Pos) {if(a[End] > b[End]) ++Emp; ++End;}
    }
    Ans = min(Ans, Li[max(Tmp, max(Pre[End - 1], Suf[End]))] - Li[i + 1]);
  }
  printf("%u\n", Ans);
  return Wild_Donkey;
}

Day \(-10\) Mar 24, 2022, Thursday

最短負環

需要知道 Floyd 的本質是一個鄰接矩陣的卷積 \(n\) 次冪. 卷積 \(\otimes\) 定義為:

\[(A \otimes B)_{i, j} = \min(\min_{k = 1}^{n}(A_{i, k} + B_{k, j}), min(A_{i, j}, B_{i, j})) \]

如果鄰接矩陣 \(A\)\(\otimes\) 卷積 \(i\) 次方表示為 \(A^i\), 那麼 \((A^i)_{j, k}\) 表示的就是 \(j\)\(k\) 的小於等於 \(i\) 條邊的最短路.

我們進行一次卷積運算需要 \(O(n^3)\), 所以進行 \(O(\log n)\) 次計算求出所有鄰接矩陣的二的整數冪, 最後倍增地求出現負環 (\((A^{Ans})_{i, i} < 0\)) 的最小冪次 \(Ans\) 即可.

int Pool[1080005], *TopP(Pool);
unsigned m, n, N, Size, C, D, t, Lgn(1);
unsigned Cur(0), Ans(0);
struct Matrix {
  int *a;
  inline void CP(const Matrix& x) { memcpy(a, x.a, Size); }
  inline void Constr () { a = TopP, TopP += N;}
  inline void Constr (const Matrix& x) {Constr(), CP(x); }
  inline void INF () { memset(a, 0x3f, Size); }
  inline char Chk () { for (unsigned i(0); i < N; i += (n + 1)) if(a[i] < 0) return 1; return 0; }
  inline void Prt () {
    printf("Matrix Pool %u\n", a - Pool);
    for (unsigned i(0); i < N; i += n) { for (unsigned j(0); j < n; ++j) printf("%d ", a[i + j]); putchar(0x0A); }
  }
  inline Matrix operator *(const Matrix& x) {
    Matrix Rt;
    Rt.Constr(), Rt.INF();
    for (int *i(Rt.a), I(0); I < N; I += n) for (int j(0); j < n; ++j, ++i) {
      for (int k(0), K(0); k < n; ++k, K += n) *i = min(*i, a[I + k] + x.a[K + j]);
      *i = min(a[I + j], min(*i, x.a[I + j]));
    }
    return Rt;
  }
}A, B[10], Tmp;
signed main() {
  n = RD(), m = RD(), Size = ((N = n * n) << 2), A.Constr(), A.INF();
  for (unsigned i(1); i <= m; ++i) C = RD() - 1, D = RD() - 1, A.a[(C * n) + D] = RDsg();
  B[0].Constr(A);
  for (unsigned i(2); i <= n; i <<= 1, ++Lgn) B[Lgn] = B[Lgn - 1] * B[Lgn - 1]; --Lgn;
  A.INF(), Tmp.Constr(), Cur = 0;
  for (unsigned i(Lgn); ~i; --i) {
    Cur += (1 << i), Tmp.CP(A * B[i]), TopP -= N;
    if(Tmp.Chk()) Cur -= (1 << i);
    else A.CP(Tmp), Ans = Cur + 1;
  }
  printf("%u\n", (Ans > n) ? 0 : Ans);
  return Wild_Donkey;
}

CF1656

這次的 A 還是很簡單的, 轉化為數軸上有 \(n\) 個點, 則問題轉化為從 \(i\) 到任意一點 \(k\) 再到 \(j\) 的路程等於 \(i\)\(j\) 的路程. 相當於 \(i\)\(j\) 的路上經過了所有點. 因此輸出最大值和最小值的下標即可.

其實把 B 題任意一個刪除方案的數字按刪除順序排列, 記為 \(a_1, a_2,...,a_n\). 那麼最後留下的數字一定是 \(a_n - a_{n - 1}\). 因此我們只要判斷給定的數中是否存在一個數減另一個等於 \(k\) 的情況即可.

C 的策略是把所有數變成 \(0\)\(1\). 如果序列裡面沒有 \(1\), 我們從大到小取模成 \(0\) 即可. 接下來只討論有 \(1\) 的情況.

如果有 \(1\) 也有 \(0\), 那麼這一定是不行的, 我們沒法動 \(1\)\(0\). 如果沒有 \(0\), 那麼只要序列中沒有相鄰的數字即可從大到小取模它減 \(1\), 這樣就都變成 \(1\) 了. 如果序列中有相鄰的數字則不行.

D 比較折磨, 對於一個 \(k\), 如果是選模 \(k\) 等於不同的 \(k\) 個值的正整數, 則最小的方案一定是 \(1, 2, 3,...,k\), 由於我們只關心總和, 所以 \(k\) 其它的可行的方案只是在這個基礎上加 \(k\) 罷了. 因此對於 \(n\), 我們認為 \(k\) 可以作為答案的充要條件是存在自然數 \(x\) 使得, \(n = \dfrac{k(k + 1)}2 + xk\). 等式兩邊同時乘以 \(2\), 變形得到 \(2n = k(k + 2x + 1)\).

這啟示我們可以設 \(a = 2x + 1\), 因為 \(x\) 是任意自然數, 所以這個 \(a\) 是任意正奇數. 我們把 \(2n\) 分解為因數 \(2^b\), \(c\), 要求是 \(c\) 是個奇數.

我們需要找出一個 \(k\), 使得 \(k(k + a) = 2n\), 因為 \(a\) 是個奇數, 所以 \(k\)\(k + a\) 的奇偶性不同.

對於 \(c = 1\) 的情況, \(2n = 2^b\). 找不到 \(2n\) 的奇因數, 所以無解.

其他情況, 對於 \(c < 2^b\) 的情況, 我們設 \(k = c\), 則 \(2^b\) 一定可以寫成 \(c + a\) 的形式, 構造出了一個解.

對於 \(c > 2^b\) 的情況, 我們設 \(k = 2^b\), 則 \(c\) 一定可以寫成 \(2^b + a\) 的形式, 構造出了一個解.

因此除了 \(2n = 2^b\) 的情況, 都有解.

E 的思維難度比 D 是小的, 但是一眼如果出不來也是非常困難的. 從刪除點 \(x\) 開始考慮, 它的每個兒子的子樹和需要相等, 並且所有點的總和減去自己的子樹和要等與兒子的子樹和. 如果我們使深度為偶數的點的子樹和為 \(1\), 使深度為奇數的點的子樹和為 \(-1\), 使所有點的總和也就是根的子樹和為 \(0\), 即可滿足題目的要求.

如果刪除的是根, 它的所有兒子深度相同, 所以子樹和也相同.

如果刪除的是奇深度的點, 它的兒子的子樹和都是 \(1\), 它的子樹和是 \(-1\), 總和 \(0\) 減去 \(-1\) 也等於 \(1\), 所以合法.

如果刪除的是偶深度的點, 它的兒子的子樹和都是 \(-1\), 它的子樹和是 \(1\), 總和 \(0\) 減去 \(1\) 也等於 \(-1\), 所以合法.

最後只要考慮如何構造這種樹即可, 一個顯然的方法是 DFS, 每次確定了子樹中所有點的權值最後規定子樹根的權值.

其實也有更好的方法: 對於葉子, 我們直接賦給他們對應的值即可, 對於它們的父親, 設兒子數量為 \(a\), 則這個點的絕對值就是 \(a + 1\), 符號就是它目標的符號, 這樣和兒子抵消之後它們的權值就是 \(1\) 了. 對於根, 我們可以先按深度要求將它的子樹和搞成 \(-1\), 最後再給它加 \(1\).

於是我們發現: 每個點的絕對值等於它的度數, 並且每個點的符號取決於它的深度. 這樣即可高效確定權值了. 並且每個點的度數不會比點數多, 符合值域要求.
省選日記 Day-14 - Day-10