题解 P4364 【[九省联考2018]IIIDX】
dengyaotriangle
2021-02-16 15:45:57
**UPD 2021/3/26 感谢 @LiM_817 发现了一些笔误,已修改**
我认为其它题解中 $f$ 数组(也就是“右边有多少空位”之类的表述)的定义多少不太明了或有问题,于是写一个我认为清楚一些的:
首先很容易想到一个(只在 $d_i$ 互不相同时正确) 的贪心,这个贪心的思路以及反例参考其它题解。
正解则是考虑**按位确定**:考虑从 $1$ 到 $n$ 依次确定该点选了哪个值,确定 $i$ 这个位置的值时,我们选取满足以下条件的值:
1. 使得这个东西确定之后,存在一种 $i+1$ 到 $n$ 的分配方案,使得总方案合法。
2. 尽可能大。
容易证明这个过程结束之后取到的就是字典序最大的合法方案。(这个方法对于其它求字典序最大值的方案也适用,是一个非常套路的东西)
那么接下来的思路将会是这样的:
- 既然是逐位确定,那我们考虑动态确定,动态维护接下来确定什么合法
- 确定了一个点不仅仅确定了这个值,其实相当于限制了它的子树内的点都 $\geq x$,或者说,这是对之后的尚未确定的值进行限定。
- 我们发现上述限定是充分必要的,于是可以直接维护它作为是否合法的判据。
- 根据限制满足的贪心性质,发现这个东西可以奇妙地使用线段树维护。
------------
考虑本题中我们如何判定一个值的选取是否合法。
若我们新确定了某个位置的值,考虑确定了它之后,会怎么影响接下来的点取值的合法性。
考虑限制:一个子树内的所有点要 $\geq$ 其根。
令一个点子树大小为 $s_u$
那我们知道,若这个位置 $u$ 确定是 $x$,那么其子树内必须也有 $s_u$ 个 $\geq x$ 的数。
而且这个条件还是充分必要的:若找得到 $s_u$ 个 $\geq x$ 的数,那么一定存在分配方案使得 $u$ 这颗子树合法。
而我们发现我们确定 $u$ 时,其子树内还肯定没有已经确定了的地方,所以它带来的限制就是,要有 $s_u$ 个,$\geq x$ 的数分配给这棵子树。注意之前说明的条件充要,所以这个限制是**完备**的,或者说,**等价于原条件的**。
所以我们引入的限制就形如:**需要拿走 $a_i$ 个 $\geq b_i$ 的数**。
但是我们发现,这个条件其实需要在确定 $u$ 的直接孩子的时候进行去除。
也就是说,每当我们发现一个节点的父亲有着上述限制,须将其删除,并且换成一个 “需要拿走一个 $d_i$”——因为父亲的值已经确定,所以我们需要把这个值去掉一个。
(刚开始就去除没有任何问题,因为孩子是一个连续区间,所以这个条件在开始被满足就一定一直被满足)
而直观上来讲,我们干的事情其实是:**把整棵子树的限制,分成根的限制和这颗子树挂在根上每棵子树的限制。**
所以我们还需要支持限制的 **删除操作**,以及形如 **需要拿走一个 $=b_i$ 的数** 的操作
限制说完了,怎么判断哪些值满足限制?若我们现在想给 $u$ 这个点分配值:
我们发现,我们只需要找到最大的值 $p$,使得存在满足条件的一种拿去方案,使得还剩下 $s_u$ 个 $\geq p$ 的数。
考虑令 $f_i$ 代表只考虑 $b_j\geq i$ 的限制,还剩下多少个 $\geq i$ 的数。考虑 $\min_{i\leq k} f_i$ ,我们发现这代表最多还剩下几个 $\geq k$ 的数。
原因是因为为了让剩下的 $\geq k$ 的更多,自然是从 $b_j$ 从大到小考虑每个限制,然后每次都拿最小的,然后其实类似一个不断对于剩下的数压栈弹栈的过程,考虑限制的时候,先压进来加入新来的数,然后每次选最小的弹掉,自然地,栈的大小的最小值就是答案。而栈的大小就是 $f_i$,若看不懂可以随便选一组限制手动模拟一下。
那么我们发现我们已经会求这个”最大的合法值了“:考虑使用线段树维护 $f$,一个限制就相当于 $[1,b_i]$ 的值 $-a_i$,然后找到最大的符合限制的值就是找到最靠右的,前缀 $\min\geq s_u$ 的位置,这个可以通过线段树二分实现。
而最后剩下的一个操作,需要拿走一个 $=b_i$ 的数 ,对应在 $f$ 数组上是 $[1,b_i]$ 的值 $-1$,也可以维护。
而 $f_i$ 的初始值就是 $d$ 中 $\geq i$ 的数的个数。
其实这个操作和另一个修改操作的形式一模一样,但一个是 $\geq b_i$,一个是 $=b_i$,其实这个的道理在于根据上面的贪心论述,我们每次都是拿尽可能小的,而 $=b_i$ 的限制存在的前提是一定能取到一个 $=b_i$ 的(因为需要合法),所以 $=b_i$ 的限制与 $\geq $ 其实一样,所以二者体现在 $f$ 上的影响是一模一样的。
参考代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
//dengyaotriangle!
const int maxn=500005;
int n,m;double k;
int val[maxn],sval[maxn],cnt[maxn];
struct node{
int tg,mi;
node* c[2];
}pool[maxn<<1];int ps;
void pu(node* rt){
rt->mi=min(rt->c[0]->mi,rt->c[1]->mi);
}
void pd(node* rt){
rt->mi+=rt->tg;
if(rt->c[0]){
rt->c[0]->tg+=rt->tg;
rt->c[1]->tg+=rt->tg;
}
rt->tg=0;
}
node* bt(int l,int r){
node* nw=pool+ps++;
if(l==r){
nw->mi=cnt[l];
}else{
nw->c[0]=bt(l,(l+r)>>1);nw->c[1]=bt(((l+r)>>1)+1,r);
pu(nw);
}
return nw;
}
void chg(node* rt,int cl,int cr,int l,int r,int d){
int cm=(cl+cr)>>1;
pd(rt);
if(l==cl&&r==cr)rt->tg+=d,pd(rt);
else if(r<=cm)chg(rt->c[0],cl,cm,l,r,d),pd(rt->c[1]),pu(rt);
else if(l>cm)chg(rt->c[1],cm+1,cr,l,r,d),pd(rt->c[0]),pu(rt);
else chg(rt->c[0],cl,cm,l,cm,d),chg(rt->c[1],cm+1,cr,cm+1,r,d),pu(rt);
}
int find(node* rt,int cl,int cr,int s){
int cm=(cl+cr)>>1;
pd(rt);
if(cl==cr){return cl-1;}
else{
pd(rt->c[0]);int w=rt->c[0]->mi;
if(w<s)return find(rt->c[0],cl,cm,s);
else return find(rt->c[1],cm+1,cr,s);
}
}
int ans[maxn];
int sz[maxn];
bool vis[maxn];
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>val[i];
sort(val+1,val+1+n);
for(int i=1;i<=n;i++)sval[i]=val[i];
m=unique(sval+1,sval+1+n)-sval;
for(int i=1;i<m;i++)cnt[i]=upper_bound(val+1,val+1+n,sval[i])-lower_bound(val+1,val+1+n,sval[i]);
for(int i=m-1;i>=1;i--)cnt[i]+=cnt[i+1];
node* rt=bt(1,m);
for(int i=n;i>=1;i--){
sz[i]++;
sz[(int)floor(i/k)]+=sz[i];
}
vis[0]=1;
for(int i=1;i<=n;i++){
int fa=(int)floor(i/k);
if(!vis[fa]){
vis[fa]=1;
chg(rt,1,m,1,ans[fa],sz[fa]-1);
}
int v=find(rt,1,m,sz[i]);
ans[i]=v;
chg(rt,1,m,1,v,-sz[i]);
}
for(int i=1;i<=n;i++)cout<<sval[ans[i]]<<' ';
return 0;
}
```