#10471. 「2020-10-02 提高模擬賽」灌溉 (water)
阿新 • • 發佈:2020-10-25
題面:#10471. 「2020-10-02 提高模擬賽」灌溉 (water)
假設只有一組詢問,我們可以用二分求解:二分最大距離是多少,然後找到深度最大的結點,並且把它的\(k\)倍祖先的一整子樹刪掉,看一下一共要刪幾次,顯然滿足單調性。
現在要詢問所有取值。上面二分的過程啟發我們可以反過來,通過列舉答案,然後找到答案對應哪些詢問。顯然對於當前\(\text{ans}\),一次刪除最少刪掉\(ans+1\)個點,最多刪\(\frac{n}{ans+1}\)次,因此是一個調和級數\(\frac{1}{1}+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+....+\frac{1}{n} -> n\ln(n)\)
這個線段樹的實現比較巧妙,\(\text{lazytag}\)有三種取值,\(0\)表示沒有操作,\(1\)表示要把孩子們全刪了,\(2\)表示要把孩子們全弄回來。若一個點的懶標記大於零,就直接用它的懶標記覆蓋它兒子們的懶標記。為了實現恢復操作,要額外存下每個點最初始的值用作恢復用。
int tree[maxn * 4], val[maxn * 4], fir[maxn * 4]; /// val: 0 -> 無要求; 1 -> 要求填滿; 2 -> 要求刪掉 int pushup(int x, int y) { return dep[x] < dep[y] ? y : x; } void spread(int x) { if (val[x] == 1) { val[x << 1] = val[x << 1 | 1] = 1; tree[x << 1] = fir[x << 1]; tree[x << 1 | 1] = fir[x << 1 | 1]; } if (val[x] == 2) { val[x << 1] = val[x << 1 | 1] = 2; tree[x << 1] = tree[x << 1 | 1] = 0; } val[x] = 0; } void build(int x, int l, int r) { val[x] = true; if (l == r) { fir[x] = tree[x] = pnt[l]; return; } int mid = (l + r) >> 1; build(x << 1, l, mid); build(x << 1 | 1, mid + 1, r); fir[x] = tree[x] = pushup(tree[x << 1], tree[x << 1 | 1]); } void erase(int x, int l, int r, int ll, int rr) { //tree[x] *= val[x]; //tree[x] = fir[x] * val[x]; if (ll <= l && r <= rr) { tree[x] = 0; val[x] = 2; return; } spread(x); int mid = (l + r) >> 1; if (ll <= mid) { erase(x << 1, l, mid, ll, rr); } if (rr > mid) { erase(x << 1 | 1, mid + 1, r, ll, rr); } tree[x] = pushup(tree[x << 1], tree[x << 1 | 1]); } int calc(int k) { //insert(1, 1, 1, 1, n); val[1] = 1; tree[1] = fir[1]; int ret = 0; while (tree[1]) { int del = Kfat(tree[1], k); erase(1, 1, n, dfn[del], dfn[del] + sze[del] - 1); ret++; } return ret; }