题解 LOJ2472 「九省联考 2018」IIIDX

如果\(\lfloor\frac{i}{k}\rfloor\neq 0\),就从\(i\)\(\lfloor\frac{i}{k}\rfloor\)连边,则可以得到一个森林。问题转化为:给森林里每个节点安排一个点权,在保证后代的点权\(\geq\)祖先点权的前提下,使按编号排列时的字典序最大。

把所有权值按从小到大排序。

自然想到的一种贪心是,把编号最大的一段,留给以\(1\)为根的树。例如,以\(1\)为根的树大小为\(\text{sz}_1\),则我们把\(val_{n-\text{sz}_1+1}\dots val_n\)留给以\(1\)为根的树,并且显然,\(1\)号节点的权值此时就是\(val_{n-\text{sz}_1+1}\)。以此类推,按编号从小到大,每个节点(的子树)都得到连续的一段长度等于其子树大小的权值。容易发现,由于这棵树的性质,深度小的节点的编号,一定小于深度大的节点的编号,所以直接按编号顺序从小到大确定即可。

这种贪心在权值无重复时显然是正确的。但如果有重复的权值,就会出问题。例如:\(n=4;k=2;a=(1,1,1,2)\)。正确的答案应该是:\((1,1,2,1)\)。而我们贪心会求出:\((1,1,1,2)\)

为什么会出错?因为要求后代对祖先是大于等于,而不是严格大于。因此,如果有多个值和当前根上的值相同,则当前根取的位置可以向前挪一点。这样,不会改变当前根上的取值,但能把更多更大的值让给其他节点。这里的其他节点,指的是不在当前根的子树内,但编号比当前根子树内的点小的节点。如此调整,可使答案的字典序更大。

具体来说,设当前根为\(u\)(从小到大枚举\(u\),这样才能贪心地保证字典序),其子树大小为\(\text{sz}_u\)。则要放在\(u\)上的值,是最大的值\(v\),满足:\(\geq v\)的、尚未使用的值的数量\(\geq \text{sz}_u\)。然后我们会取走一个\(v\),同时在\(\geq v\)的值中取走\(\text{sz}_u-1\)个。但我们此时并不确定,这\(\text{sz}_u-1\)个数具体要取哪些值。形象地说,我们要在\(v\)后面,为\(u\)的子树占一些位置,但由于空位数量可能不止\(\text{sz}_u\)个,所以暂时不能确定占哪些位置。

把权值离散化。然后容易想到用线段树维护“取走权值”的操作,用线段树上二分,找到第一个\(\geq \text{sz}_u\)的后缀。一种想法是:线段树每个叶子节点,存一个\(\{0,1\}\)的变量,表示该值是否还未被取走。然后用线段树维护区间和。但是你会发现,这种方法,用于处理:在\(v\)后面占\(\text{sz}_u\)个坑,而不确定具体占哪些坑时,是比较棘手的。

我们需要更巧妙的做法。记\(c_v\)表示值\(v\)还剩多少个。设\(f_v=\sum_{i\geq v}c_i\)。则我们实际要在线段树上二分的,就是最大的、\(f_v\geq\text{sz}_u\)的位置\(v\)。考虑维护\(f\)数组。在执行“在\(v\)后面占\(\text{sz}_u\)个坑”这个操作时,显然的是:\(f_{1\dots v}\)都需要减去\(\text{sz}_u\)。但是大于\(v\)的值,它们的\(f_i\)如何变化呢?首先,\(f_i\)的上限是:\(\min_{j=1}^{i}f_j\)。并且,这个上限总是能够达到的(只要紧挨着\(v\),取\(\text{sz}_u\)个数,后面的\(f_i\)就会达到这个上限)。不妨先对所有\(i>v\),令\(f_i=\min_{j=1}^{i}f_j\)。然后按编号从小到大,我们会继续处理和\(u\)同一层的一些节点,然后再进入\(u\)的子树。此时,有可能与\(u\)同一层的节点,已经用掉了\(v\)后面的一些位置,但我们至少能保证\(v\)后面有\(\text{sz}_u\)个坑是预留好的。同时,这种让\(f_i\)顶到“上限”的方式,又能保证在取和\(u\)同一层的节点时,充分发挥贪心性,尽其所能最大化字典序。

具体实现时,我们用线段树记录\(f\)数组,同时维护区间\(\min\)。修改操作是区间加,即对\(f_{1\dots v}\)同时加上\(-\text{sz}_u\)。在线段树上二分位置\(v\)时,注意到真实的\(f\)值,其实是表面上这个\(f\)数组的前缀\(\min\)即可。另外要注意:二分之前,别忘了把\(u\)的父亲为\(u\)占的坑补回来。

时间复杂度\(O(n\log n)\)

参考代码:

在LOJ查看

//problem:LOJ2472
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=5e5;
int n,a[MAXN+5],fa[MAXN+5],sz[MAXN+5],vals[MAXN+5],cnt_v,c[MAXN+5],ans[MAXN+5];
double K;
vector<int>G[MAXN+5];
bool vis[MAXN+5];
struct SegmentTree{
	int mn[MAXN*4+5],tag[MAXN*4+5];
	//支持区间加
	//支持线段树上二分:最靠后的一个"前缀min">=x的位置
	void push_up(int p){
		mn[p]=min(mn[p<<1],mn[p<<1|1]);
	}
	void upd(int p,int v){
		mn[p]+=v;
		tag[p]+=v;
	}
	void push_down(int p){
		if(tag[p]){
			upd(p<<1,tag[p]);
			upd(p<<1|1,tag[p]);
			tag[p]=0;
		}
	}
	void build(int p,int l,int r){
		tag[p]=0;
		if(l==r){
			mn[p]=c[l];//>=l的数有c[l]个
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		push_up(p);
	}
	void range_add(int p,int l,int r,int ql,int qr,int v){
		if(ql<=l&&qr>=r){
			upd(p,v);return;
		}
		push_down(p);
		int mid=(l+r)>>1;
		if(ql<=mid)range_add(p<<1,l,mid,ql,qr,v);
		if(qr>mid)range_add(p<<1|1,mid+1,r,ql,qr,v);
		push_up(p);
	}
	int query(int p,int l,int r,int x){
		if(l==r)return mn[p]>=x?l:-1;
		push_down(p);
		int mid=(l+r)>>1;
		if(mn[p<<1]<x){
			int res=query(p<<1,l,mid,x);
			push_up(p);
			return res;
		}
		else{
			int res=query(p<<1|1,mid+1,r,x);
			push_up(p);
			return res!=-1?res:mid;
		}
	}
	SegmentTree(){}
}T;
void dfs(int u){
	vis[u]=1;
	sz[u]=1;
	for(int i=0;i<SZ(G[u]);++i){
		int v=G[u][i];
		dfs(v);
		sz[u]+=sz[v];
	}
}
int main() {
	ios::sync_with_stdio(false);
	cin>>n>>K;
	for(int i=1;i<=n;++i){
		fa[i]=floor((double)i/K);
		if(fa[i]!=0)G[fa[i]].pb(i); 
		cin>>a[i];
		vals[i]=a[i];
	}
	sort(vals+1,vals+n+1);
	cnt_v=unique(vals+1,vals+n+1)-(vals+1);
	for(int i=1;i<=n;++i){
		a[i]=lob(vals+1,vals+cnt_v+1,a[i])-vals;
		c[a[i]]++;
	}
	for(int i=cnt_v-1;i>=1;--i)c[i]+=c[i+1];
	T.build(1,1,cnt_v);
	for(int i=1;i<=n;++i)if(!vis[i])dfs(i);
	for(int i=1;i<=n;++i){
		if(fa[i]){
			T.range_add(1,1,cnt_v,1,ans[fa[i]],sz[i]);
		}
		ans[i]=T.query(1,1,cnt_v,sz[i]);
		assert(ans[i]!=-1);
		T.range_add(1,1,cnt_v,1,ans[i],-sz[i]);
	}
	for(int i=1;i<=n;++i)cout<<vals[ans[i]]<<" \n"[i==n];
	return 0;
}
posted @ 2020-04-24 18:11  duyiblue  阅读(154)  评论(0编辑  收藏  举报