CF1477D Nezzar and Hidden Permutations(構造)
題目大意
你需要構造出兩個排列 p, q,滿足 m 個限制,第 i 個限制爲 $ (p_{x_i}-p_{y_i})\times (q_{x_i}-q_{y_i}) \ge 0$,最大化 \(\sum [p_i \neq q_i]\)
\(1 \le n,m \le 5\times 10^5\)
解題思路
還是牛逼的猜結論和構造題,但是我哪一步都做不出來 😭
我們把每個限制可以看做是一條邊,首先發現如果一個點的度數是 n - 1,必然有 \(p_x=q_x\),我們讓 \(p_x=n\),刪掉這個點和關於它的限制,做 n - 1 階的問題,直到最大度數小於 n - 1。
這時候有結論:必定有解使得每個 \(p_i \neq q_i\)!
考慮這樣一種基本結構,存在一個點(成爲關鍵點)和其他的所有點都沒有邊,那麼這個點設成 (1, n),其他點設成 (k + 1, k) 可以滿足所有的條件,那麼如果我們可以把整個圖分成若干個集合,每個集合都可以表示爲這樣的基本結構,那麼這個問題就解決了。
幸運的是,這是可以的,考慮兩個點 (x, y),其中 x 還沒有加入任何集合,滿足 x,y 之間沒有邊。
分類討論一下:
- 如果 y 沒有加入集合,那麼將 x,y 新建一個集合;
- 如果 y 在一個大小爲 2 的集合中或者 y 是集合中的關鍵點,我們將 y 設爲關鍵點,把 x 加入集合;
- 否則,我們可以把 y 從集合中刪掉,和 x 新建一個集合。
這個構造方式的條件僅是需要滿足任何一個點都可以找到另外一個點和它沒有邊!
這樣我們簡單模擬即可,注意到是一堆集合操作,可以用 set 輕鬆維護,看起來我的代碼比別人稍短一些。
源代碼:https://codeforces.com/contest/1477/submission/107069108(目前最短)
#include <set>
const int N = 505000;
set<int> ed[N], S[N];
int id[N], rt[N], p[N], q[N], T, m, n;
void work(void) {
read(n), read(m);
for (int i = 1;i <= n; ++i) ed[i].clear(), id[i] = 0;
for (int i = 1, x, y;i <= m; ++i)
read(x), read(y), ed[x].insert(y), ed[y].insert(x);
int all = 0, cnt = 0;
for (int i = 1;i <= n; ++i) {
if (ed[i].size() == n - 1) { p[i] = q[i] = ++all; continue; }
if (id[i]) continue;
int to = 1;
ed[i].insert(n + 1);
for (auto y : ed[i]) {
if (i == to) ++to;
if (y != to) break;
++to;
}
auto mk = [&](int x, int y) {
rt[id[x] = id[y] = ++cnt] = x;
S[cnt].insert(x), S[cnt].insert(y);
};
if (!id[to]) { mk(i, to); continue; }
int ak = id[to];
if (S[ak].size() == 2 || rt[ak] == to) S[ak].insert(i), rt[id[i] = ak] = to;
else S[ak].erase(to), mk(i, to);
}
for (int i = 1;i <= cnt; ++i) {
p[rt[i]] = ++all;
for (auto t: S[i])
if (t != rt[i]) q[t] = all, p[t] = ++all;
q[rt[i]] = all, S[i].clear(), rt[i] = 0;
}
for (int i = 1;i <= n; ++i) write(p[i], " \n"[i == n]);
for (int i = 1;i <= n; ++i) write(q[i], " \n"[i == n]);
}