洛谷 P4364 [九省联考 2018] IIIDX

题目背景

Osu 听过没?那是 Konano 最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏。现在,他在世界知名游戏公司 KONMAI 内工作,离他的梦想也越来越近了。

这款音乐游戏内一般都包含了许多歌曲,歌曲越多,玩家越不易玩腻。同时,为了使玩家在游戏上氪更多的金钱花更多的时间,游戏一开始一般都不会将所有曲目公开,有些曲目你需要通关某首特定歌曲才会解锁,而且越晚解锁的曲目难度越高。

题目描述

这一天,Konano 接到了一个任务,他需要给正在制作中的游戏《IIIDX》安排曲目的解锁顺序。游戏内共有 �n 首曲目,每首曲目都会有一个难度 �d,游戏内第 �i 首曲目会在玩家 Pass 第 ⌊��⌋⌊ki​⌋ 首曲目后解锁(⌊�⌋⌊x⌋ 为下取整符号)若 ⌊��⌋=0⌊ki​⌋=0,则说明这首曲目无需解锁

举个例子:当 �=2k=2 时,第 11 首曲目是无需解锁的(⌊12⌋=0⌊21​⌋=0),第 77 首曲目需要玩家 Pass 第 ⌊72⌋=3⌊27​⌋=3 首曲目才会被解锁。

Konano 的工作,便是安排这些曲目的顺序,使得每次解锁出的曲子的难度不低于作为条件需要玩家通关的曲子的难度,即使得确定顺序后的曲目的难度对于每个 �i 满足 ��≥�⌊��⌋di​≥d⌊ki​⌋​。

当然这难不倒曾经在信息学竞赛摸鱼许久的 Konano。那假如是你,你会怎么解决这份任务呢?

输入格式

第 11 行 11 个正整数 �n 和 11 个小数 �,�k,n 表示曲目数量,�k 其含义如题所示。

第 22 行 �n 个用空格隔开的正整数 �d,表示这 �n 首曲目的难度。

输出格式

输出 11 行 �n 个整数,按顺序输出安排完曲目顺序后第 �i 首曲目的难度。

若有多解,则输出 �1d1​ 最大的;若仍有多解,则输出 �2d2​ 最大的,以此类推。

输入输出样例

输入 #1复制

4 2.0
114 514 1919 810

输出 #1复制

114 810 514 1919

说明/提示

测试点编号�n�k�d特殊限制
111≤�≤101≤n≤10�=2k=21≤�≤1001≤d≤100保证 ��di​ 互不相同
221≤�≤101≤n≤10�=3k=31≤�≤1001≤d≤100保证 ��di​ 互不相同
331≤�≤101≤n≤10�=1.1k=1.11≤�≤1001≤d≤100保证 ��di​ 互不相同
441≤�≤101≤n≤10�=�k=n1≤�≤1001≤d≤100保证 ��di​ 互不相同
551≤�≤101≤n≤101<�≤1001<k≤1001≤�≤1001≤d≤100保证 ��di​ 互不相同
661≤�≤101≤n≤101<�≤1001<k≤1001≤�≤1001≤d≤100保证 ��di​ 互不相同
771≤�≤20001≤n≤2000�=2k=21≤�≤1091≤d≤109保证 ��di​ 互不相同
881≤�≤20001≤n≤2000�=2k=21≤�≤1091≤d≤109
991≤�≤20001≤n≤2000�=3k=31≤�≤1091≤d≤109保证 ��di​ 互不相同
10101≤�≤20001≤n≤2000�=3k=31≤�≤1091≤d≤109
11111≤�≤20001≤n≤20001<�≤1091<k≤1091≤�≤1091≤d≤109保证 ��di​ 互不相同
12121≤�≤20001≤n≤20001<�≤1091<k≤1091≤�≤1091≤d≤109
13131≤�≤5000001≤n≤500000�=2k=21≤�≤1091≤d≤109
14141≤�≤5000001≤n≤500000�=3k=31≤�≤1091≤d≤109
15151≤�≤5000001≤n≤5000001<�≤1091<k≤1091≤�≤1091≤d≤109保证 ��di​ 互不相同
16161≤�≤5000001≤n≤5000001<�≤1091<k≤1091≤�≤1091≤d≤109保证 ��di​ 互不相同
17171≤�≤5000001≤n≤5000001<�≤1091<k≤1091≤�≤1091≤d≤109
18181≤�≤5000001≤n≤5000001<�≤1091<k≤1091≤�≤1091≤d≤109
19191≤�≤5000001≤n≤5000001<�≤1091<k≤1091≤�≤1091≤d≤109
20201≤�≤5000001≤n≤5000001<�≤1091<k≤1091≤�≤1091≤d≤109

首先可以一眼得到一个做法,将权值从大到小排序,把长度为子树大小的一段按子树编号从小到大丢给它们,递归下去得到答案。

这种做法在��di​不重复的时候是正确的,那��di​要是有相同的数呢?

有可能可以将编号�x子树里一个大的权值与编号�+1x+1的子树根的权值替换,使得�x的权值依然是能取得的最大数且子树内的数都比�x的权值大,同时�+1x+1子树内的点也还能标满≥≥�+1x+1权值的权值。

那怎么做呢?先把给定权值从大到小排序,线段树上维护每个权值左边(包括本身)还能取的权值的个数��Ci​。

当取好一个点�x的权值时,需要给它子树内的点预留取的权值,这些权值显然在�x取得权值的左边,但是我们并不知道取的是哪些权值,所以只把�x取得的权值右边(包括本身)的��Ci​减去�x子树大小����[�]size[x],每次取一个点�y的权值时,只需要在线段树上找到最大权值���val满足���val所在位置右边(包括本身)的所有��≥����[�]Ci​≥size[y],且���val尽可能靠右即可,这个在线段树上二分就能做到。

如果一个点有父亲,求它的权值时要把它父亲为子树预留的大小去掉,需要注意的是,每个父亲预留的大小只要去掉一次,写的时候容易忽略这一点。

 代码如下:

#include<iostream> 
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath> 
#include<algorithm> 
using namespace std;
const int maxn=500010, inf=1e9;
struct poi{int mn, delta;}tree[maxn<<2];
int n, N;
double k;
int a[maxn], b[maxn], ans[maxn], size[maxn], fa[maxn], cnt[maxn];
inline void read(int &k)
{
	int f=1; k=0; char c=getchar();
	while(c<'0' || c>'9') c=='-'&&(f=-1), c=getchar();
	while(c<='9' && c>='0') k=k*10+c-'0', c=getchar();
	k*=f;
}
inline bool cmp(int a, int b){return a>b;}
inline void addone(int x, int delta){tree[x].delta+=delta; tree[x].mn+=delta;}
inline void up(int x){tree[x].mn=min(tree[x<<1].mn, tree[x<<1|1].mn);}
inline void down(int x)
{
	addone(x<<1, tree[x].delta);
	addone(x<<1|1, tree[x].delta);
	tree[x].delta=0;
}
void build(int x, int l, int r)
{
	if(l==r){tree[x].mn=l; return;}
	int mid=(l+r)>>1;
	build(x<<1, l, mid); build(x<<1|1, mid+1, r);
	up(x);
}
int query(int x, int l, int r, int k)
{
	if(l==r) return (tree[x].mn>=k?l:l+1);
	down(x);
	int mid=(l+r)>>1;
	if(k<=tree[x<<1|1].mn) return query(x<<1, l, mid, k);
	else return query(x<<1|1, mid+1, r, k);
}
void update(int x, int l, int r, int cl, int cr, int delta)
{
	if(cl<=l && r<=cr){tree[x].mn+=delta; tree[x].delta+=delta; return;}
	down(x);
	int mid=(l+r)>>1;
	if(cl<=mid) update(x<<1, l, mid, cl, cr, delta);
	if(cr>mid) update(x<<1|1, mid+1, r, cl, cr, delta);
	up(x);
}
int main()
{
	read(n); scanf("%lf", &k);	
	for(int i=1;i<=n;i++) read(a[i]);
	sort(a+1, a+1+n, cmp);
	for(int i=n-1;i;i--) 
	if(a[i]==a[i+1]) cnt[i]=cnt[i+1]+1; else cnt[i]=0;
	for(int i=1;i<=n;i++) fa[i]=(int)floor(i/k), size[i]=1;
	for(int i=n;i;i--) size[fa[i]]+=size[i];
	build(1, 1, n);
	for(int i=1;i<=n;i++)
	{
		if(fa[i]&&fa[i]!=fa[i-1]) update(1, 1, n, ans[fa[i]], n, size[fa[i]]-1);
		int x=query(1, 1, n, size[i]); 
		x+=cnt[x]; cnt[x]++; x-=(cnt[x]-1); ans[i]=x; 
		update(1, 1, n, x, n, -size[i]);
	}
	for(int i=1;i<=n;i++) printf("%d ", a[ans[i]]);
    return 0;
}

拜拜! 

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值