Codeforces Round 967 (Div. 2) A~C 題解(未完待續)
唐完了,B 構造不會,C 互動不會,整場爆切 \(1\) 題喜提 \(-115\) rating 強勢上灰!我還會什麼?
I. 2001A - Make All Equal
先找出答案的下界。設眾數為 \(x\),其出現的次數為 \(\operatorname{cnt}(x)\)。由於每次操作只能刪除一個數,答案不可能小於 \(n - \operatorname{cnt}(x)\)。
下面證明這個下界是可以達到的。用 \(y\) 代表所有的非眾數,那麼在非眾數被刪光之前,序列可以表示成 \(x \dots x, y \dots y, \dots\) 之類 \(x\) 與 \(y\) 的連續段交替出現的形式。只需證明一定存在一對相鄰元素(這裡的相鄰指環上的相鄰)滿足前者小於後者即可。
反證法,如果不存在,則 \(x > y > \cdots\)。由於序列是一個環,所以可以匯出 \(x > x\),這是不可能的,故得證。
程式碼
II. 2001B - Generate Permutation
我是傻逼,我沒做出來這題
先考察滿足要求的序列有什麼性質。可以發現(事實上,我並沒有發現),對於從左往右的打字員來說,假設他已經打出了 \(x\),要打 \(x + 1\),只有 \(\operatorname{pos}_x > \operatorname{pos}_{x+1}\) 時他才要按一次回車(其中 \(\operatorname{pos}_x\) 表示 \(x\) 的下標)。因此他按回車的次數 \(c_1 = \sum_{i=1}^{n-1} [\operatorname{pos}_x > \operatorname{pos}_{x+1}]\)。
(這裡應該模擬一下打字的過程才能更順理成章地發現這個結論:假設我已經打出了 \(x\),要打 \(x + 1\),我能直接移動然後打出來,還是必須得回車?顯然,只有 \(x + 1\) 在 \(x\) 前面時才要回車,這就對應了 \(\operatorname{pos}_x > \operatorname{pos}_{x+1}\)。這樣想的話,這個結論並不難得出,但是我為什麼沒想到呢?)
同樣的,對於從右往左的打字員,只有 \(\operatorname{pos}_x < \operatorname{pos}_{x+1}\) 時他才要按一下回車,因此他按回車的次數 \(c_2 = \sum_{i=1}^{n-1} [\operatorname{pos}_x < \operatorname{pos}_{x+1}]\)。
合法的序列應該滿足 \(c_1 = c_2\)。顯然,對於 \(x\) 和 \(x + 1\),要麼 \(\operatorname{pos}_x > \operatorname{pos}_{x+1}\) 成立,要麼 \(\operatorname{pos}_x < \operatorname{pos}_{x+1}\) 成立,所以 \(c_1 + c_2 = n - 1\),因此 \(c_1 = c_2 = \dfrac{n-1}{2}\)。這就說明 \(n\) 是偶數時一定沒有合法方案。
\(n\) 是奇數時,可以手模出許多合法的方案。以 \(n = 7\) 為例,一種好想的思路是:先用一堆降序的數把 \(c_1\) 給佔滿,比如 \(7, 6, 5, 4\)。那麼剩下的數必須是升序的,否則會讓 \(c_1\) 變大。直接填進去即可: \(1, 2, 3, 7, 6, 5, 4\) 就是可行的。(你會發現 \(7, 6, 5, 4, 1, 2, 3\) 是不可行的,因為 \((3, 4)\) 這一對讓 \(c_1\) 變大了)。
因此,\(n\) 是奇數時, \({\color{blue}1, 2, \dots, \left\lfloor \dfrac{n-1}{2} \right\rfloor,} {\color{red}n, n-1, \dots, \left\lfloor \dfrac{n+1}{2} \right\rfloor}\) 是一個合法的方案。這裡用不同顏色區分構造時分別填入的兩段。
void solve()
{
int n;
cin >> n;
if((n & 1) == 0)
{
cout << -1 << endl;
return;
}
vector<int> ans(n);
iota(ans.begin(), ans.end(), 1);
reverse(ans.begin() + (n/2), ans.end());
for(int x: ans) cout << x << ' ';
cout << endl;
}
程式碼
III. 2001C - Guess The Tree
下面是官方題解的思路。我自己想不出來這個做法,所以不知道用什麼語言來引入。
把點劃分為兩個點集 \(A\) 和 \(B\),\(A\) 中的點是知道其內部邊集的點,\(B\) 中的點是不知道的。
初始時,任意把一個點加入 \(A\) 中(例如 \(1\)),\(B\) 是剩下的 \(n-1\) 個點。
我們的策略是:每次把一個新的點加入到 \(A\) 中。這個點必須和原來 \(A\) 中的某個點有連邊,否則根本無法把它加入 \(A\)。
任選點 \(u \in A\),\(v \in B\),查詢 \(u, v\) 的中點 \(x\)。如果 \(x \in A\),則令 \(u \gets x\),否則令 \(v \gets x\)。這裡的要義是,時刻保證 \(u \in A\) 且 \(v \in B\)。最終當 \(x = u\) 時,就表明 \(u\),\(v\) 之間有邊相連,可以更新答案的邊集。由於我們保證了 \(v \in B\),所以可以把 \(v\) 加入到 \(A\) 中,並在 \(B\) 中刪掉 \(v\)。
這裡的查詢類似二分,每次找到一個新點的查詢次數不會超過 \(\log n\),總次數不超過 \(n \log n\),可以透過此題。
但是我不知道我自己該如何想出這些東西