1. 程式人生 > >[九省聯考 2018]IIIDX

[九省聯考 2018]IIIDX

for pan cnblogs 方式 navi 等於 spa 貪心 技術分享

Description

題庫鏈接

給你 \(n+1\) 個節點的一棵樹,節點編號為 \(0\sim n\)\(0\) 為根。邊集為 \(\mathbb{E}=\left\{(u,v)\big|\forall i\in[1,n],\left(\left\lfloor\frac{i}{k}\right\rfloor,i\right)\right\}\) 。給出 \(n\) 個待選序號,讓你為 \(1\sim n\)\(n\) 個節點編號,第 \(i\) 號節點編為 \(a_i\),要求父親編號小於等於兒子的編號。求滿足要求的序列 \(a_1,a_2,\cdots,a_n\) 中字典序最大的一個。

\(1\leq n\leq 500000\)

Solution

直接遞歸貪心回溯時選一個未選的最大值是錯的。

考慮為什麽會出錯,依舊舉一個例子:

4 2
1 1 1 2

畫成圖就是:技術分享圖片

如果按照剛才的貪心方式,我們會先將 \(2\) 賦給 \(4\) 號節點,再將 \(1\) 賦給 \(2\) 號節點。這樣就錯了,考慮為什麽?

因為容易發現,不論怎麽分配, \(2\) 號節點一定只能賦為 \(1\) ,這時被選的數中有兩個 \(1\) ,等於說我們可以讓 \(4\) 號點取 \(1\) ,這樣是更優的。

那麽之前的貪心就錯了,但不過它提供了一個思路,就是對於一個節點的兒子們,一定是先盡可能將標號小的兒子的子樹用大的標號標。唯一需要處理的就是子樹的根節點的標號可能有多個相同的備選。

我們將被選數從大到小排序。一個節點按之前的方式編號,我們就要選與這個編號相同的最靠右的一個。這樣能保證最優。

我們不用遞歸,我們只需要枚舉節點時為其子樹預留節點即可。

考慮線段樹維護這樣的一個數組 \(f\)\(f_i\) 表示 \(i\) 位置前有多少個數可選。

每次查詢的時候只要找到這樣的一個最靠左的位置 \(x\) ,使得 \(\forall i,f_i\geq size_i\) ,其中 \(size_i\) 為當前節點子樹的大小。

然後將 \(x\) 賦為與這個編號相同的最靠右的一個的位置。那麽這個位置的值就是被選值。

處理完之後,我們還要對 \(x\) 之後的 \(f\) 數組進行修改。

以上操作都可以用線段樹維護。

值得註意的是由於處理到一個節點的時候,如果它有父親,那麽要將其父親的預留的額度刪去。

如果仍有不理解,可參見ppt。

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 500000+5;

int n, a[N], fa[N], nxt[N], size[N], ans[N]; double k;
struct Segment_tree {
#define lr(o) (o<<1)
#define rr(o) (o<<1|1)
    int minn[N<<2], tag[N<<2];
    void pushdown(int o) {
        minn[lr(o)] += tag[o], tag[lr(o)] += tag[o];
        minn[rr(o)] += tag[o], tag[rr(o)] += tag[o];
        tag[o] = 0;
    }
    void build(int o, int l, int r) {
        if (l == r) {minn[o] = l; return; } int mid = (l+r)>>1;
        build(lr(o), l, mid), build(rr(o), mid+1, r);
        minn[o] = min(minn[lr(o)], minn[rr(o)]);
    }
    void update(int o, int l, int r, int a, int b, int k) {
        if (a <= l && r <= b) {minn[o] += k, tag[o] += k; return; }
        pushdown(o); int mid = (l+r)>>1;
        if (a <= mid) update(lr(o), l, mid, a, b, k);
        if (b > mid) update(rr(o), mid+1, r, a, b, k);
        minn[o] = min(minn[lr(o)], minn[rr(o)]);
    }
    int query(int o, int l, int r, int k) {
        if (l == r) return minn[o] >= k ? l : l+1;
        pushdown(o); int mid = (l+r)>>1;
        if (k <= minn[rr(o)]) return query(lr(o), l, mid, k);
        else return query(rr(o), mid+1, r, k);
    }
}T;
bool comp(const int &a, const int &b) {return a > b; }

void work() {
    scanf("%d%lf", &n, &k);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]); sort(a+1, a+n+1, comp);
    for (int i = n; i >= 1; i--) {
        nxt[i] = i, fa[i] = floor(1.*i/k);
        ++size[i]; size[fa[i]] += size[i];
        if (a[i] == a[i+1]) nxt[i] = nxt[i+1];
    }
    T.build(1, 1, n);
    for (int i = 1; i <= n; i++) {
        if (fa[i] && fa[i] != fa[i-1]) T.update(1, 1, n, ans[fa[i]], n, size[fa[i]]-1);
        int loc = nxt[T.query(1, 1, n, size[i])]; ans[i] = loc;
        T.update(1, 1, n, loc, n, -size[i]);
    }
    for (int i = 1; i <= n; i++) printf("%d ", a[ans[i]]);
}
int main() {work(); return 0; } 

[九省聯考 2018]IIIDX