P4364 [九省联考 2018] IIIDX(线段树、贪心)

本文介绍了一种利用线段树和贪心策略解决动态规划问题的方法。通过先对值进行降序排序,再维护一棵线段树存储空余位置,按BFS序枚举节点并选择最靠右的空位,实现了解决相等值的情况。详细阐述了代码实现过程,包括线段树的更新和查找操作。适合对算法和数据结构感兴趣的读者深入理解。
摘要由CSDN通过智能技术生成

解析

感觉不至于黑的题。
然而我并不会做

d i d_i di 互不相同的时候直接无脑贪心即可,这样55分的好成绩就到手了。(交完发现可以骗到60)
滚榜级的良心分了属于是。

考虑有相同时如何做。
先把值降序排序,然后维护一棵线段树,存储每个点左侧还空余的位置
按照 bfs序 枚举节点(其实就是1-n),每次找到空余位置不少于 s i z siz siz 的位置。
当有相等时选择占用最靠右的一个,这里我使用了离散化后堆存储下标来实现,感觉非常优美。
确定位置后位置之后的空余都要减去子树大小。
注意到,由于前面没有修改,我们实际上寻找的应该是满足后缀最小值不小于 s i z siz siz 的最左位置
维护一个最小值,线段树二分即可实现。
记得算儿子的时候把父亲子树的影响删去。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=5e5+100;
const int mod=998244353;
const int C=26;

inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}

int n,m;

#define mid ((l+r)>>1)
#define ls (k<<1)
#define rs (k<<1|1)
int mn[N<<2],laz[N<<2];
inline void pushup(int k){
	mn[k]=min(mn[ls],mn[rs]);
	return;
}
inline void add(int k,int w){
	mn[k]+=w;laz[k]+=w;
}
inline void pushdown(int k){
	int o=laz[k];laz[k]=0;
	if(!o) return;
	add(ls,o);add(rs,o);
}
void build(int k,int l,int r){
	if(l==r){
		mn[k]=l;return;
	}
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(k);
}
int find(int k,int l,int r,int w){
	if(l==r){
		return mn[k]>=w?l:0;
	}
	pushdown(k);
	if(mn[rs]<w) return find(rs,mid+1,r,w);
	else{
		int d=find(ls,l,mid,w);
		return d?d:mid+1;
	}
}
void change(int k,int l,int r,int x,int y,int w){
	if(x<=l&&r<=y){
		add(k,w);return;
	}
	pushdown(k);
	if(x<=mid) change(ls,l,mid,x,y,w);
	if(y>mid) change(rs,mid+1,r,x,y,w);
	pushup(k);
}

int d[N];
double k;
int fa[N],siz[N];
vector<int>v[N];
int w[N];

priority_queue<int>id[N];
bool jd[N];
int q[N],cnt;
bool cmp(int x,int y){
	return x>y;
}
int pl[N];
signed main(){
#ifndef ONLINE_JUDGE
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
#endif
	scanf("%d %lf",&n,&k);
	for(int i=1;i<=n;i++) q[i]=d[i]=read();	
	
	cnt=n;
	sort(q+1,q+1+cnt);
	cnt=unique(q+1,q+1+cnt)-q-1;
	sort(d+1,d+1+n,cmp);	
	for(int i=1;i<=n;i++){
		d[i]=lower_bound(q+1,q+1+cnt,d[i])-q;
		id[d[i]].push(i);
		//printf("i=%d d=%d\n",i,d[i]);
	}
	
	for(int i=n;i>=1;i--){
		siz[i]++;
		fa[i]=floor(i/k);
		siz[fa[i]]+=siz[i];
	}
	
	build(1,1,n);
	for(int i=1;i<=n;i++){
		int f=fa[i];
		if(f&&!jd[f]){
			jd[f]=1;
			change(1,1,n,pl[f],n,siz[f]-1);
		}
		int o=find(1,1,n,siz[i]);
		pl[i]=id[d[o]].top();
		//printf("i=%d fa=%d siz=%d find=%d pl=%d\n",i,fa[i],siz[i],o,pl[i]);
		id[d[o]].pop();
		w[i]=q[d[pl[i]]];
		change(1,1,n,pl[i],n,-siz[i]);
	}
	for(int i=1;i<=n;i++) printf("%d ",q[d[pl[i]]]);
	return 0;
}
/*
8 3.000
31 15 2 54 97 65 46 61
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值