如果(lfloorfrac{i}{k} floor eq 0),就从(i)向(lfloorfrac{i}{k} floor)连边,则可以得到一个森林。问题转化为:给森林里每个节点安排一个点权,在保证后代的点权(geq)祖先点权的前提下,使按编号排列时的字典序最大。
把所有权值按从小到大排序。
自然想到的一种贪心是,把编号最大的一段,留给以(1)为根的树。例如,以(1)为根的树大小为( ext{sz}_1),则我们把(val_{n- ext{sz}_1+1}dots val_n)留给以(1)为根的树,并且显然,(1)号节点的权值此时就是(val_{n- ext{sz}_1+1})。以此类推,按编号从小到大,每个节点(的子树)都得到连续的一段长度等于其子树大小的权值。容易发现,由于这棵树的性质,深度小的节点的编号,一定小于深度大的节点的编号,所以直接按编号顺序从小到大确定即可。
这种贪心在权值无重复时显然是正确的。但如果有重复的权值,就会出问题。例如:(n=4;k=2;a=(1,1,1,2))。正确的答案应该是:((1,1,2,1))。而我们贪心会求出:((1,1,1,2))。
为什么会出错?因为要求后代对祖先是大于等于,而不是严格大于。因此,如果有多个值和当前根上的值相同,则当前根取的位置可以向前挪一点。这样,不会改变当前根上的取值,但能把更多更大的值让给其他节点。这里的其他节点,指的是不在当前根的子树内,但编号比当前根子树内的点小的节点。如此调整,可使答案的字典序更大。
具体来说,设当前根为(u)(从小到大枚举(u),这样才能贪心地保证字典序),其子树大小为( ext{sz}_u)。则要放在(u)上的值,是最大的值(v),满足:(geq v)的、尚未使用的值的数量(geq ext{sz}_u)。然后我们会取走一个(v),同时在(geq v)的值中取走( ext{sz}_u-1)个。但我们此时并不确定,这( ext{sz}_u-1)个数具体要取哪些值。形象地说,我们要在(v)后面,为(u)的子树占一些位置,但由于空位数量可能不止( ext{sz}_u)个,所以暂时不能确定占哪些位置。
把权值离散化。然后容易想到用线段树维护“取走权值”的操作,用线段树上二分,找到第一个(geq ext{sz}_u)的后缀。一种想法是:线段树每个叶子节点,存一个({0,1})的变量,表示该值是否还未被取走。然后用线段树维护区间和。但是你会发现,这种方法,用于处理:在(v)后面占( ext{sz}_u)个坑,而不确定具体占哪些坑时,是比较棘手的。
我们需要更巧妙的做法。记(c_v)表示值(v)还剩多少个。设(f_v=sum_{igeq v}c_i)。则我们实际要在线段树上二分的,就是最大的、(f_vgeq ext{sz}_u)的位置(v)。考虑维护(f)数组。在执行“在(v)后面占( ext{sz}_u)个坑”这个操作时,显然的是:(f_{1dots v})都需要减去( ext{sz}_u)。但是大于(v)的值,它们的(f_i)如何变化呢?首先,(f_i)的上限是:(min_{j=1}^{i}f_j)。并且,这个上限总是能够达到的(只要紧挨着(v),取( ext{sz}_u)个数,后面的(f_i)就会达到这个上限)。不妨先对所有(i>v),令(f_i=min_{j=1}^{i}f_j)。然后按编号从小到大,我们会继续处理和(u)同一层的一些节点,然后再进入(u)的子树。此时,有可能与(u)同一层的节点,已经用掉了(v)后面的一些位置,但我们至少能保证(v)后面有( ext{sz}_u)个坑是预留好的。同时,这种让(f_i)顶到“上限”的方式,又能保证在取和(u)同一层的节点时,充分发挥贪心性,尽其所能最大化字典序。
具体实现时,我们用线段树记录(f)数组,同时维护区间(min)。修改操作是区间加,即对(f_{1dots v})同时加上(- ext{sz}_u)。在线段树上二分位置(v)时,注意到真实的(f)值,其实是表面上这个(f)数组的前缀(min)即可。另外要注意:二分之前,别忘了把(u)的父亲为(u)占的坑补回来。
时间复杂度(O(nlog n))。
参考代码:
//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]]<<"
"[i==n];
return 0;
}