IOI2018 機械娃娃

2022-05-05 12:18:09 字數 2968 閱讀 9737

看到的時候感到很不可做,因為所有的開關都要狀態歸零。因此可以得到兩分的好成績。

……然後 yhx-12243 說:這不是線段樹優化建圖嗎?

於是我獲得了啟發,會做了……

還不是和上次一樣,通過提示做出這種互動題的?

我還是太菜了

以下魔改自我的思考過程(一開始想對每乙個觸發器配一組開關決策下一步,然後聽說用線段樹直接想到一組開關決策整顆樹了)

其實前半部分的思考貌似沒用,直接套到整棵樹上是適用的

因為狀態要清零,考慮清空這個狀態。

考慮每個觸發器連向乙個開關,來決策下一步走什麼,因為和二進位制有關,所以我們構造乙個線段樹類似的。

假設出現 \(s\) 次,記 \(l = 2^k \geq s\) 使得 \(k\) 最小。

那麼我們前 \(l - s\) 次就開個到根的邊,後面 \(s\) 次就可以直接走後繼了。

那麼如果走了 \(l\) 步,線段樹內所有點都遍歷到了。

注意到 \(l\) 是二的冪,跑完後所有非葉節點區間長度都是偶數,即遍歷到偶數次,即清零了。這也說明了為什麼不用普通線段樹。

所以要把葉子結點全部扔掉,不然就會有節點只遍歷到一次。如果扔掉葉子,那麼就是一棵大小為 \(l - 1\) 的樹。

注意特判一下最後乙個點的後繼是 \(0\)。

這樣點數是 \(2n\) 的 (對於乙個 \(s\),有 \(s \leq l < 2s\)),可以獲得 \(50\) 分。

考慮優化這個,發現很多的點是沒有用的,即整個區間都會再次回到根。那麼要訪問到這個區間的時候,不如提前回到根。即刪掉這個點,父親連向自己的邊改為連向根。

但是發現優化力度不大,這是因為這道題的遍歷方式使得遍歷的點交錯了起來。

因為最後乙個點是在右邊的,那麼我們把線段樹右邊的那 \(s\) 個點取來,欽定它們是出邊,然後根據先前遍歷的順序把值欽定一下就好。

我們只選了右邊的點,優化力度很明顯了。

對於 \(s = 5\), \(l = 8\),淡色是葉子,打鉤的是有用的點。(機房沒帶數字板請見諒,其他軟體不會用,求dalao推薦)

可以證明對於乙個 \(n\) ,點數是 \(n - 1 + \lceil \log_2 n \rceil\)。考慮對於相同的 \(l\), 考慮 \(n\) 個葉子,至少要 \(n - 1\) 個點,而多出來的是單獨一條鏈,也就是最後乙個元素向上的鏈。這個鏈長度小於等於 \(\lceil \log_2 n \rceil\)。

所以這樣子就能得到較為優秀的分數了。

然而對於每乙個觸發器,我們都會多乙個 \(\log\)。那麼為什麼不放在一起呢?這樣正好符合正解的複雜度。

考慮用乙個統一的開關線段樹維護這些觸發器,然後觸發器回到決策線段樹的開頭。

注意觸發器不能回到起點,因為乙個觸發器可能出現多次,那麼我們很難決定它是最後乙個的時候怎麼做。

為了回到起點,我們增加乙個虛點,或者換句話說,就是往規定的觸發器序列末尾加乙個 \(0\)。

一樣的,因為遍歷了二的冪次,所以所有開關歸零,經過開關次數 \(o\left(n \log n\right)\) 大概 \(3 \times 10^6\),也不會**。

開關個數 \(o\left(n + \lceil \log_2 \left(n + 1\right) \rceil\right)\),因為judger對這個 \(\log\) 的界卡的不緊,大概可以大 \(1\),所以剛好能通過此題。

看到有些人用bitreverse,的確這樣子就是bitreverse後的不斷+1,可以給各種操作帶來方便。

#include "doll.h"

#include typedef std::vectorvi;

const int maxn = 262145;

const int spe = 19260817;

int idx[maxn], xs[maxn], ys[maxn];

int qli[maxn];

int typ[maxn], cnto[maxn], tot, lim, n;

int tli[maxn], t0t, cnt[maxn];

void qry(int u, int l, int r)

int mid = l + r >> 1;

!typ[u] ? qry(u << 1, l, mid) : qry(u << 1 | 1, mid + 1, r);

typ[u] ^= 1;

}void relable(int u, int l, int r)

int mid = l + r >> 1;

relable(u << 1 | 1, mid + 1, r);

relable(u << 1, l, mid);

}int txt;

int build(int l, int r)

int mid = l + r >> 1;

int tl = build(l, mid), tr = build(mid + 1, r);

if (tl == spe && tr == spe) return spe;

int now = ++txt;

xs[now] = tl, ys[now] = tr;

return -now;

}void create_circuit(int m, vi a) );

for (int i = 1; i <= n; ++i)

cnt[tli[i]] = i;

std::copy(a.begin(), a.end(), qli + 1);

vi c(m + 1, 0), x, y;

int rt = build(1, lim);

for (int i = 1; i <= txt; ++i)

for (int i = 0; i <= m; ++i) c[i] = rt;

x.assign(xs + 1, xs + 1 + txt);

y.assign(ys + 1, ys + 1 + txt);

answer(c, x, y);

}

IOI2018 組合動作

題目 我是垃圾 最開始覺得可以三次問出第一位,之後還有 n 1 位和 n 1 次詢問,只需要一次確定一位就好了 之後就發現我是垃圾,上來直接press ab 如果不是 0 那麼首位就是 a 或 b 否則就是 x 或 y 之後再問一次就好了 之後我們需要用 n 次確定 n 1 位,於是就開始大躍進了 ...

IOI2018 組合動作 題解

傳送門 loj2863 果然簽到題就是好玩 首先我們可以猜2次來猜出第乙個字元是什麼,後面的串就和這個字元沒關係了。假設剩下的三個字元是tmp 0 tmp 1 tmp 2 目前已知答案串的長度為i 1 i 1i 1的字首是ans。考慮第i ii位 i n 1 i leq n 1 i n 1 我們可以...

IOI2018 排座位 線段樹

ioi2018seat 這題思路真的很神。原題編號從0開始,很不舒服,我們按從1開始的講。發現只需要判斷 1,i 這些數是否組成了乙個矩陣。那麼我們能不能用線段樹,第i個葉子節點存前i個數的資訊來判斷前i個數能否組成矩陣呢?有的人可能會想到第i個葉子節點維護前i個數中最左上的點和最右下的點,判斷時直...