题解 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)\)。
参考代码:
//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;
}