• bzoj 5249 [2018多省省队联测] IIIDX


    bzoj 5249 [2018多省省队联测] IIIDX

    Solution

    首先想到贪心,直接按照从大到小的顺序在后序遍历上一个个填

    但是这样会有大问题,就是有相同的数的时候,会使答案不优

    比如考虑 ((1, 2)(1, 3)(2, 4)) 这样一棵树,并且点权是 ({1,1,1,2})

    那么直接贪心会使得答案为 (v_1=1,v_2=1,v_3=1,v_4=2),但是实际上最优解为 (v_1=1,v_2=1,v_3=2,v_4=1)

    问题出在我们先考虑 (v_2) 的时候,直接填上了第三个 (1),导致他的儿子 (v_4) 只有第四个数 (2) 一种填法,而事实上最优解里面 (v_2) 对应的是第二个 (1)

    所以做出修改:将权值按照从大到小的顺序排的时候,直接贪心当前填的数为 (v) 时,我们选择最靠右的 (v) 填入,也就是填入最小的 (v)

    那么怎么维护这个玩意呢?本来因为所有子树对应的区间都连续,只需要记录 (l)(r) 即可,现在不连续了,我们只能对每个数记录下它左侧(大于等于它的数)有多少个被预定了,那么考虑当前点的时候这些预定的位置就不能填

    所以具体操作如下:

    1. 使用线段树维护每个位置左侧还有几个数可以使用,区间的值为其左子区间和右子区间权值的 (min),记录 ( ext{nxt}) 数组表示当前位置距离和他值相等且最靠右的位置的距离,如果当前这个位置就是最靠右的,那么它的 ( ext{nxt}) 表示它左侧第一个未被选中的和它值相同的位置与它之间的距离
    2. 设当前点为 (v),当前点的父亲为 (u)
    3. 假如 (v)(u) 的第一个儿子,那么处理 (u) 的时候为 (u) 的子树预定的 (size_u) 个位置即将被使用了,我们需要把预定取消,也就是在 (ans_u) 及其右侧所有数的值加上 (size_u - 1)
    4. 在线段树上二分,找到第一个权值大于等于 (size_v) 的数,下面调整位置
    5. 根据上面的做法,我们需要把当前数对应上最右侧的未被选中的位置,我们用 ans += nxt[ans]( ext{ans}) 先放到它最右侧的位置上,再使用 ans -= nxt[ans] ,将 ( ext{ans}) 放到当前的值中最靠右的未被选中的位置上,最后 nxt[ans]++ 表示这个位置及其右侧所有和它相等的一段都已经被选了,所以下次再找到这个位置的时候只能走到下一段值上去
    6. 预定它的子树,就是给 (ans_v) 及其右侧的所有数的值减去 (size_v)

    Code

    // Copyright lzt
    #include<stdio.h>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<vector>
    #include<map>
    #include<set>
    #include<cmath>
    #include<iostream>
    #include<queue>
    #include<string>
    #include<ctime>
    using namespace std;
    typedef long long ll;
    typedef std::pair<int, int> pii;
    typedef long double ld;
    typedef unsigned long long ull;
    typedef std::pair<long long, long long> pll;
    #define fi first
    #define se second
    #define pb push_back
    #define mp make_pair
    #define rep(i, j, k)  for (register int i = (int)(j); i <= (int)(k); i++)
    #define rrep(i, j, k) for (register int i = (int)(j); i >= (int)(k); i--)
    #define Debug(...) fprintf(stderr, __VA_ARGS__)
    
    inline ll read() {
      ll x = 0, f = 1;
      char ch = getchar();
      while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
      }
      while (ch <= '9' && ch >= '0') {
        x = 10 * x + ch - '0';
        ch = getchar();
      }
      return x * f;
    }
    
    #define lc (i << 1)
    #define rc (i << 1 | 1)
    const double eps = 1e-8;
    const int maxn = 500500;
    struct Node {
      int l, r, val, tag;
    } tr[maxn << 2];
    int n; double k;
    int a[maxn], ans[maxn], fa[maxn], sz[maxn], nxt[maxn];
    
    inline void build(int i, int l, int r) {
      tr[i].l = l; tr[i].r = r;
      if (l == r) {
        tr[i].val = l; tr[i].tag = 0;
        return;
      }
      int md = (l + r) >> 1;
      build(lc, l, md); build(rc, md + 1, r);
      tr[i].val = min(tr[lc].val, tr[rc].val);
    }
    inline void pushdown(int i) {
      tr[lc].val += tr[i].tag;
      tr[rc].val += tr[i].tag;
      tr[lc].tag += tr[i].tag;
      tr[rc].tag += tr[i].tag;
      tr[i].tag = 0;
    }
    inline void add(int i, int p, int v) {
      if (p <= tr[i].l) {
        tr[i].tag += v; tr[i].val += v;
        return;
      }
      pushdown(i);
      add(rc, p, v);
      if (tr[lc].r >= p) add(lc, p, v);
      tr[i].val = min(tr[lc].val, tr[rc].val);
    }
    int ask(int i, int x) {
      if (tr[i].l == tr[i].r) return tr[i].val >= x ? tr[i].l : tr[i].l + 1;
      pushdown(i);
      if (tr[rc].val >= x) return ask(lc, x);
      else return ask(rc, x);
    }
    
    void work() {
      scanf("%d %lf", &n, &k);
      build(1, 1, n);
      rep(i, 1, n) a[i] = read();
      sort(a + 1, a + n + 1, greater<int>());
      rrep(i, n - 1, 1) if (a[i] == a[i + 1]) nxt[i] = nxt[i + 1] + 1;
      rrep(i, n, 1) {
        fa[i] = (int)(i / k + eps);
        sz[i]++; sz[fa[i]] += sz[i];
      }
      rep(i, 1, n) {
        if (fa[i] && fa[i] != fa[i - 1]) add(1, ans[fa[i]], sz[fa[i]] - 1);
        ans[i] = ask(1, sz[i]);
        ans[i] += nxt[ans[i]]; ans[i] -= nxt[ans[i]];
        nxt[ans[i]]++;
        add(1, ans[i], -sz[i]);
      }
      rep(i, 1, n) printf("%d ", a[ans[i]]);
    }
    
    int main() {
      #ifdef LZT
        freopen("in", "r", stdin);
      #endif
    
      work();
    
      #ifdef LZT
        Debug("My Time: %.3lfms
    ", (double)clock() / CLOCKS_PER_SEC);
      #endif
    }
    

    Review

    贪心可以得到 (60) 分,正解比较难想到,JS考场上只有yzl过了这道题

    关键在于发现贪心的错误在于没有选择最靠右的相同的数,就是条件被加紧了,我们需要放松条件

    然后用线段树维护的过程比较自然,( ext{nxt}) 数组很精妙,完成了找到最靠右的未被选中的值得任务

  • 相关阅读:
    pixi.js 简单交互事件(点击、缩放、平移)
    w3school
    并发(Concurrent)与并行(Parallel)的区别
    CTime获得当前电脑时间
    NX二次开发-UFUN获得体的表面积,体积,重心等UF_MODL_ask_mass_props_3d
    Jvm故障问题排查以及Jvm调优总结
    jmap命令详解----查看JVM内存使用详情
    jstack命令解析
    年轻带Young GC算法示意图
    【JVM】jstat命令详解---JVM的统计监测工具
  • 原文地址:https://www.cnblogs.com/wawawa8/p/10158848.html
Copyright © 2020-2023  润新知