2019.02.26【九省联考2018】【BZOJ5249】【洛谷P4364】IIIDX(贪心)(树状数组)

BZOJ传送门

洛谷传送门


解析:

首先我们有一个很显然的 错误 的贪心,按照字典序和siz依次将最大的 s i z siz siz个权值分配给以某个点为根的子树,然后根节点选择最小的权值后递归处理。

很遗憾,它只能处理所有 d d d不同的情况。

比如这组数据:

4 3.0
1 1 2 2

答案应该是:

1 2 2 1

然而贪心是:

1 1 2 2

考虑为什么会出现这种情况,这时候,贪心的结果出来 1 1 1 2 2 2的值是相等的,然而这个相等的值其实可以传给后面 1 1 1的子树,这样 2 2 2就有机会得到更大的值。

所以我们需要处理一下贪心。

小的尽可能向后丢。

所以我们可以先排个序,处理出目前未分配的权值中和当前权值相等的有哪些。

然后需要查找目前所有可以直接分配权值(即所有祖先权值已经确定)的点中,后面有至少 c n t cnt cnt个的第一个。

后缀和。。。

所以线段树维护当前可决策的点标号,算出后缀和后在树上二分找到每个就行了。

当然,由于维护的只有后缀和,所以可以直接树状数组,顺便学习了一下树状数组上的二分写法。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define pc putchar
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline double getdb(){
		re char c;
		while(!isdigit(c=gc()));re double x=c^48;
		while(isdigit(c=gc()))x=x*10+(c^48);
		if(c^'.')return x;
		re double y=1.0;
		while(isdigit(c=gc()))x+=(y/=10)*(c^48);
		return x;
	}
	
	cs int Rlen=5e6+5;
	
	char obuf[Rlen],*p=obuf;
	inline void put_char(char c){*p++=c;}
	struct io{~io(){fwrite(obuf,1,p-obuf,stdout);}}out;
	
	inline void outint(int a){
		static char ch[23],top;
		if(a==0)pc('0');
		while(a)ch[++top]=a-a/10*10,a/=10;
		while(top)pc(ch[top--]^48);
	}
}
using namespace IO;

cs int N=5e5+5;

int s[1<<19|1],r;

inline void add(int pos,int val){
	for(int re i=pos;i<=r;i+=i&-i)s[i]+=val;
}

inline int find(int val){
	val=s[r]-val;
	int res=r,tmp=0,d=r>>1;
	for(;d;d>>=1)s[res]>val?res-=d:(tmp=res,val-=s[res],res|=d);
	return s[res]>val?tmp:res;
}

int n;double k;
int d[N],son[N],fa[N],siz[N],cnt[N],ans[N];

signed main(){
	n=getint();k=getdb();
	for(r=1;r<n;r<<=1);
	for(int re i=1;i<=n;++i)son[fa[i]=i/k]=i,d[i]=getint();
	sort(d+1,d+n+1);
	for(int re i=n;i;--i)siz[fa[i]]+=++siz[i],cnt[i]=d[i]==d[i+1]?cnt[i+1]+1:1;
	for(int re i=1;i<=son[0];++i)add(i,siz[i]);
	for(int re i=1;i<=n;++i){
		int pos=find(cnt[i])+1;
		ans[pos]=d[i];
		add(pos,-siz[pos]);
		for(int re ch=son[pos-1]+1;ch<=son[pos];++ch)
		add(ch,siz[ch]);
	}
	for(int re i=1;i<=n;++i)outint(ans[i]),pc(' ');
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值