您的位置:首页 > 其它

BZOJ_5249_Luogu_P4364_[2018多省省队联测]_IIIDX_九省联考2018_JLOI2018_线段树

2018-04-09 08:37 387 查看

BZOJ_5249_[2018多省省队联测]IIIDX_线段树

Description

【题目背景】 Osu听过没?那是Konano最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏。现在,他在世界知名游戏公司KONMAI内工作,离他的梦想也越来越近了。这款音乐游戏内一般都包含了许多歌曲,歌曲越多,玩家越不易玩腻。同时,为了使玩家在游戏上氪更多的金钱花更多的时间,游戏一开始一般都不会将所有曲目公开,有些曲目你需要通关某首特定歌曲才会解锁,而且越晚解锁的曲目难度越高。 【题目描述】 这一天,Konano接到了一个任务,他需要给正在制作中的游戏《IIIDX》安排曲目的解锁顺序。游戏内共有n首曲目,每首曲目都会有一个难度d,游戏内第i首曲目会在玩家Pass第$\lfloor i/k \rfloor$首曲目后解锁(x为下取整符号)若$\lfloor i/k \rfloor =0$,则说明这首曲目无需解锁。举个例子:当k=2时,第1首曲目是无需解锁的($\lfloor 1/2 \rfloor =0$),第7首曲目需要玩家Pass第$\lfloor 7/2 \rfloor =3$首曲目才会被解锁。Konano的工作,便是安排这些曲目的顺序,使得每次解锁出的曲子的难度不低于作为条件需要玩家通关的曲子的难度,即使得确定顺序后的曲目的难度对于每个i满足Di≥$\lfloor i/k \rfloor$ 。当然这难不倒曾经在信息学竞赛摸鱼许久的Konano。那假如是你,你会怎么解决这份任务呢

Input

第1行1个正整数n和1个小数k,n表示曲目数量,k其含义如题所示。 第2行n个用空格隔开的正整数d,表示这n首曲目的难度。 1 ≤ n ≤ 500000 1 < k ≤ 10^9 1 < d ≤ 10^9

Output

输出1行n个整数,按顺序输出安排完曲目顺序后第i首曲目的难度。 若有多解,则输出d1最大的;若仍有多解,则输出d2最大的,以此类推。

Sample Input

4 2.0
114 514 1919 810

Sample Output

114  810  514  1919   这题出的很好,感觉考场上写的和正解差的好多啊。 关键词:预定。 xqz学长打了个十分恰当的比方,我们想象占座的时候你拿了一桶纸,你需要占x个座,然后把这桶纸自然的滚过去,刚好滚了x个座,相当于都占上了,然后下一个人来了需要把你放的纸收起来,重新占座,这题其实就是这个意思。 首先可以发现题目给出的关系是一棵森林,而我们要给森林中的每个节点权值,使得每个节点的权值都要小于等于子树内任何一点的权值,并且使权值字典序最大。 建一个虚点向所有$i < k$的点连边,于是森林就成了一棵树,并处理出子树的大小$siz$。 把权值从大到小排序,对于每个点,我要选当前剩下的权值里第$siz$大的,然后给子树预定这个点前面的权值,递归处理。 这是一种错误的贪心,错误数据 4 2.0 1 1 1 2 答案是1 1 2 1而错误的贪心输出1 1 1 2。 当权值各不相同的时候这个贪心是对的,因为第$siz$大的就是我们要的,子树用比它大的那$siz-1$个即可。 而如果权值有重复的,我们要找和第$siz$大的权值相等的权值中最靠右的那个点,再给子树预定$siz-1$个,这样做能够让当前点权值尽可能大的前提下给子树预定的权值尽可能的小,这样,万一下一个要分配的点不在这个点的子树中,也能被分配尽可能大的权值。 怎么操作呢? 设$F[i]$为第i个权值左边有多少个没被预定,即可用数量。初始值$F[i]=i$。我们在线段树上维护$F[i]$和$F[i]$的最大值。 举个官方题解中的例子:9 8 7 6 5 5 5 5 5 4 3 2 假如第一个结点的子树大小为7,那么第一个结点的权值就应该是第7大的数。 初始时$F[i]=i$,我们发现$F[12]>F[11]>F[10]>F[9]>F[8]>F[7]=7$,即第$7$个点右侧的可用数量都大于$7$,我们刚好应该选择第$7$个数$5$,同时让被选的$5$的位置尽可能的靠右,于是选到了第$9$的位置。(具体做法:每个相等的值有一个$cnt$值,当前位置$x$加上$cnt[x]$刚好是权值为$a[x]$的数中最右的位置,再处理出这个位置上的数已经被拿了多少次,从$x$中减掉)。 接着第一个节点要给它的子树预定$siz-1$个位置,但我们不好确定是哪几个位置,于是$F[9]\thicksim F
$都减去$(siz-1)$即可。 对于其他结点$i$重复上述操作: $1$.找到一个位置尽可能靠前的$x$,满足$x$右边的所有$F[]$都大于等于$siz[i]$。这步查询在线段树上二分即可。 $2.$找到权值为$a[x]$中剩下的最右的位置$y$满足$a[y]=a[x]$,并把$a[y]$分配给这个点。 $3.F[y]\thicksim F
$减去$(siz-1)$ 注意处理到某个点,如果这个点的父亲的预定区间没有被撤销,需要撤销。   代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
#define N 500050
#define ls p<<1
#define rs p<<1|1
typedef double du;
int fa
,siz
,n,cnt
,a
,t[N<<2],del[N<<2],ans
;
du k;
bool used
;
bool cmp(int x,int y){return x>y; }
void build(int l,int r,int p) {
if(l==r) {
t

=l; return ; } int mid=(l+r)>>1; build(l,mid,ls); build(mid+1,r,rs); t[p]=min(t[ls],t[rs]); } void pushdown(int p) { if(del[p]) { del[ls]+=del[p]; t[ls]+=del[p]; del[rs]+=del[p]; t[rs]+=del[p]; del[p]=0; } } void update(int l,int r,int x,int y,int v,int p) { if(x<=l&&y>=r) { t[p]+=v; del[p]+=v; return ; } pushdown(p); int mid=(l+r)>>1; if(x<=mid) update(l,mid,x,y,v,ls); if(y>mid) update(mid+1,r,x,y,v,rs); t[p]=min(t[ls],t[rs]); } int query(int l,int r,int x,int p) { if(l==r) return t[p]>=x?l:l+1; pushdown(p); int mid=(l+r)>>1; if(t[rs]>=x) return query(l,mid,x,ls); else return query(mid+1,r,x,rs); } int main() { scanf("%d%lf",&n,&k); int i; for(i=1;i<=n;i++) scanf("%d",&a[i]),siz[i]=1; sort(a+1,a+n+1,cmp); for(i=n-1;i>=1;i--) { if(a[i]==a[i+1]) cnt[i]=cnt[i+1]+1; else cnt[i]=0; } for(i=1;i<=n;i++) { fa[i]=(int) floor(i/k); } for(i=n;i;i--) siz[fa[i]]+=siz[i]; build(1,n,1); for(i=1;i<=n;i++) { if(fa[i]&&!used[fa[i]]) { update(1,n,ans[fa[i]],n,siz[fa[i]]-1,1); used[fa[i]]=1; } int x=query(1,n,siz[i],1); x+=cnt[x]; cnt[x]++; x-=(cnt[x]-1); ans[i]=x; update(1,n,x,n,-siz[i],1); } int flg=0; for(i=1;i<=n;i++) { if(!flg) flg=printf("%d",a[ans[i]]); else printf(" %d",a[ans[i]]); } }

[p] 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: