[九省联考2018]IIIDX [线段树]

传送门

发现可以建出一棵树,给树分配权值,祖先比子孙的权值小,且让整棵树按标号输出权值最大

有一个比较容易想到的贪心 ----

考虑一个点 u,它的大小 为 siz[u],我们每次从能够选择的权值中挑 siz[u] 个最大的,并将最后一个赋给 u

这样做在权值没有重复的时候是对的,但有重复就会 GG

因为兄弟可以跟它的子树里面的点换,仍然满足题意,但兄弟变大,也就是答案更优

举个例子,权值为 9 9 8 7 6 6 6 6 5 5

siz[2] 为 7,那么2预留 9 9 8 7 6 6 ,然后 2 选 6 ?

不,这样不是最优,因为我们可以将 3(本身是6),与2子树中的9交换,这样 2及它的子树的值为, 6 6 6 6 7 8 9

仍然满足题意,还使 3 达到最大

我们发现可以通过线段树来完成这个预留操作

定义f[i]数组表示i号点左边(包括它自己)最多可以再放多少个点

那么f[i]数组一定是一个单调不下降的数组,我们可以用线段树来维护 f 数组和某段区间的 f 最小值

那么我们就可以每次在线段树上二分出那个最左边的位置

再通过一个cnt 数组来存储当前点向右位移几个距离能够到达使当前点最优并且能给同层的点提供一个可能更优的位置的位置

就可以解决这个问题了


#include<bits/stdc++.h>
#define N 500050
using namespace std;
int Min[N<<2], tag[N<<2];
int n, a[N], b[N], cnt[N], ans[N]; double k;
bool cmp(int x, int y){ return x > y;}
int fa[N], siz[N], vis[N];
void Pushup(int x){ Min[x] = min(Min[x<<1], Min[x<<1|1]);} 
void Build(int x, int l, int r){
	tag[x] = 0; if(l == r){ Min[x] = l; return;}
	int mid = (l+r) >> 1;
	Build(x<<1, l, mid); Build(x<<1|1, mid+1, r);
	Pushup(x);
}
void Pushnow(int x, int v){ Min[x] += v; tag[x] += v;}
void Pushdown(int x){
	if(tag[x] == 0) return;
	Pushnow(x<<1, tag[x]); Pushnow(x<<1|1, tag[x]);
	tag[x] = 0;
}
void Modify(int x, int l, int r, int L, int R, int v){
//	cout<<L<<" "<<R<<" "<<v<<endl;
	if(L<=l && r<=R){ Pushnow(x, v); return;}
	Pushdown(x); int mid = (l+r) >> 1;
	if(L<=mid) Modify(x<<1, l, mid, L, R, v);
	if(R>mid) Modify(x<<1|1, mid+1, r, L, R, v);
	Pushup(x);
}
int Find(int x, int l, int r, int k){
	if(l == r){
		if(Min[x] < k) return l+1;
		else return l;
	}
	Pushdown(x); int mid = (l+r) >> 1;
	if(Min[x<<1|1] >= k) return Find(x<<1, l, mid, k);
	else return Find(x<<1|1, mid+1, r, k);
}
int main(){
	//freopen("1.in","r",stdin);
	scanf("%d%lf", &n, &k);
	for(int i=1; i<=n; i++) scanf("%d", &a[i]), a[i];
	sort(a+1, a+n+1, cmp);
	for(int i=n-1; i>=1; i--){
		cnt[i] = (a[i] == a[i+1]) ? cnt[i+1] + 1 : 0;
	} Build(1, 1, n);
	for(int i=n; i>=1; i--){
		siz[i] += 1; int f = i / k;
		fa[i] = f; siz[f] += siz[i];
	}
	for(int i=1; i<=n; i++){
		if(fa[i] && !vis[fa[i]]){
			Modify(1, 1, n, ans[fa[i]], n, siz[fa[i]]-1);
			vis[fa[i]] = 1;
		}
		int pos = Find(1, 1, n, siz[i]); pos += cnt[pos];
		cnt[pos]++; pos -= (cnt[pos]-1);
		Modify(1, 1, n, pos, n, -siz[i]);
		ans[i] = pos; 
	} 
	for(int i=1; i<=n; i++) printf("%d ", a[ans[i]]);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值