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

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

Link

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\),记录 \(\text{nxt}\) 数组表示当前位置距离和他值相等且最靠右的位置的距离,如果当前这个位置就是最靠右的,那么它的 \(\text{nxt}\) 表示它左侧第一个未被选中的和它值相同的位置与它之间的距离
  2. 设当前点为 \(v\),当前点的父亲为 \(u\)
  3. 假如 \(v\) 是 \(u\) 的第一个儿子,那么处理 \(u\) 的时候为 \(u\) 的子树预定的 \(size_u\) 个位置即将被使用了,我们需要把预定取消,也就是在 \(ans_u\) 及其右侧所有数的值加上 \(size_u - 1\)
  4. 在线段树上二分,找到第一个权值大于等于 \(size_v\) 的数,下面调整位置
  5. 根据上面的做法,我们需要把当前数对应上最右侧的未被选中的位置,我们用 ans += nxt[ans] 将 \(\text{ans}\) 先放到它最右侧的位置上,再使用 ans -= nxt[ans] ,将 \(\text{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\n", (double)clock() / CLOCKS_PER_SEC);
  #endif
}

Review

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

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

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

原文地址:https://www.cnblogs.com/wawawa8/p/10158848.html

时间: 2024-08-11 19:25:49

bzoj 5249 [2018多省省队联测] IIIDX的相关文章

【刷题】BZOJ 5249 [2018多省省队联测]IIIDX

Description [题目背景] Osu听过没?那是Konano最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏.现在,他在世界知名游戏公司KONMAI内工作,离他的梦想也越来越近了.这款音乐游戏内一般都包含了许多歌曲,歌曲越多,玩家越不易玩腻.同时,为了使玩家在游戏上氪更多的金钱花更多的时间,游戏一开始一般都不会将所有曲目公开,有些曲目你需要通关某首特定歌曲才会解锁,而且越晚解锁的曲目难度越高. [题目描述] 这一天,Konano接到了一个任务,他需要给正在制作中的

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

Description [题目背景] Osu听过没?那是Konano最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏.现在,他在世界知名游戏公司KONMAI内工作,离他的梦想也越来越近了.这款音乐游戏内一般都包含了许多歌曲,歌曲越多,玩家越不易玩腻.同时,为了使玩家在游戏上氪更多的金钱花更多的时间,游戏一开始一般都不会将所有曲目公开,有些曲目你需要通关某首特定歌曲才会解锁,而且越晚解锁的曲目难度越高. [题目描述] 这一天,Konano接到了一个任务,他需要给正在制作中的

【刷题】BZOJ 5248 [2018多省省队联测]一双木棋

Description 菲菲和牛牛在一块n行m列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手.棋局开始时,棋盘上没有任何棋子, 两人轮流在格子上落子,直到填满棋盘时结束.落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且 这个格子的左侧及上方的所有格子内都有棋子. 棋盘的每个格子上,都写有两个非负整数,从上到下第i行中从左到右第j列的格子上的两个整数记作Aij.Bij.在 游戏结束后,菲菲和牛牛会分别计算自己的得分:菲菲的得分是所有有黑棋的格子上的Aij之和,牛牛的得分是所 有有白棋的格

BZOJ 5248: [2018多省省队联测]一双木棋

Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 439  Solved: 379[Submit][Status][Discuss] Description 菲菲和牛牛在一块n行m列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手.棋局开始时,棋盘上没有任何棋子, 两人轮流在格子上落子,直到填满棋盘时结束.落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且 这个格子的左侧及上方的所有格子内都有棋子. 棋盘的每个格子上,都写有两个非负整数,从上到下

bzoj千题计划307:bzoj5248: [2018多省省队联测]一双木棋

https://www.lydsy.com/JudgeOnline/problem.php?id=5248 先手希望先手得分减后手得分最大,后手希望先手得分减后手得分最小 棋盘的局面一定是阶梯状,且从上往下递减 可以将轮廓线作为状态,记忆化搜索 用n个数表示一个状态,第i个数表示第i行放了几个 记忆的状态表示当棋盘为这个状态时,接下来再下的最有解 记忆化搜索节省的是接下来再下的时间 #include<map> #include<cstdio> #include<cstring

bzoj5248: [2018多省省队联测]一双木棋

Description 菲菲和牛牛在一块n行m列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手.棋局开始时,棋盘上没有任何棋子, 两人轮流在格子上落子,直到填满棋盘时结束.落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且 这个格子的左侧及上方的所有格子内都有棋子. 棋盘的每个格子上,都写有两个非负整数,从上到下第i行中从左到右第j列的格子上的两个整数记作Aij.Bij.在 游戏结束后,菲菲和牛牛会分别计算自己的得分:菲菲的得分是所有有黑棋的格子上的Aij之和,牛牛的得分是所 有有白棋的格

bzoj5252 [2018多省省队联测]林克卡特树

斜率优化树形dp?? 我们先将问题转化成在树上选K+1条互不相交路径,使其权值和最大. 然后我们考虑60分的dp,直接维护每个点子树内选了几条路径,然后该点和0/1/2条路径相连 然后我们会发现最后的答案关于割的边数是一个单峰的函数,这时候事情就变得明朗起来个p 我们考虑拿一条斜率为k的直线去切这个函数,切到的点是什么?是每选一条路径额外付出k点代价时的最优解,于是我们二分这个斜率,然后直接树形dp求最优解以及位置即可,因为每次的最优解一定是上次的最优解和儿子的最优解共同转移而来的,所以我们只需

[bzoj 1911][Apio 2010]特别行动队(斜率优化DP)

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1911 分析: 首先可以的到裸的方程f[i]=max{f[j]+a*(Si-Sj)^2+b*(Si-Sj)+c} 0<j<i 简化一下方程,我们知道对于一次项,最后结果肯定是b*Sn 所以可以写成f[i]=max{f[j]+a*(Si-Sj)^2+c} 0<j<i 我们不妨设0<x<y<i,且x比y优 即f[x]+a*(Si-Sx)^2+c>f[y]+a*

bzoj 5496: [2019省队联测]字符串问题【SAM+拓扑】

有一个想法就是暴力建图,把每个A向有和他相连的B前缀的A,然后拓扑一下,这样的图是n^2的: 考虑优化建图,因为大部分数据结构都是处理后缀的,所以把串反过来,题目中要求的前缀B就变成了后缀B 建立SAM,发现在parent树中每个B能走到的A都在子树中,所以保留这个树结构,连边权为0的边: 然后在parent树上倍增找到每个AB串对应的点,因为SAM上每个对应不止一个串,所以找完之后把对应多个AB串的点拆成一条链 然后对于一对(x,y)的AB串关系,Ax对应的点向By对应的点连边权为A长度的边