【题解】 P4364 [九省联考 2018] IIIDX
首先把题目转化成给一棵树赋权值,让他成为一个小根堆,字典序最大。
考虑贪心,对于当前点 \(u\) 的子树,选择当前最大的 \(siz_u\) 个权值,然后递归求解。这样在 \(d_i\) 互不相同的时候容易发现是正确的。
如果 \(d_i\) 中存在相同的,我们发现当在决策 \(u\) 的子树时,子树内可以选择和 \(u\) 相等的权值,把大的权值让给和 \(u\) 的同深度的其他点(显然点的编号越小深度越小)。
这个时候我们就不能递归每个子树了,正确的做法是按照点的编号来决策。
先将 \(d\) 从小到大排序。
假设当前点是 \(u\) ,我们找到最大的可能权值在排好序的数组中的最小位置,让 \(u\) 选择这个位置,然后在 \(u\) 后面预留出 \(siz_u\) 个位置,这个可以通过维护 \(f_i\) 表示 \(i\) 后面最多有多少个剩余位置来实现,预留就是在 \([1,i-1]\) 区间减 \(1\) ,用线段树维护。
可是这样我们发现 \(i\) 的值不是真实的 \(f\) ,同时因为我们无法确定预留了哪些权值,所以真实的 \(f\) 没办法直接维护。
这个时候我们让之前所有预留的值都尽量靠前选(跳过 \(i\) ),这是为了让当前的决策点尽量靠后,此时的 \(f_i\) 就是维护的数组的前缀最小值,这个模拟一下这个过程就能得出。
我们发现 \(f_i\) 具有单调性,所以决策每个点的时候在线段树上二分就可以了。
注意在决策点 \(u\) 时要把父亲为他预留的位置空出来。
时间复杂度 \(O(n\log n)\)
\(\sf{Code}\)
#include<bits/stdc++.h>
#define N 2001001
#define MAX 2001
using namespace std;
typedef long long ll;
typedef double db;
const ll inf=1e18;
inline void read(ll &ret)
{
ret=0;char c=getchar();bool pd=false;
while(!isdigit(c)){pd|=c=='-';c=getchar();}
while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();}
ret=pd?-ret:ret;
return;
}
ll n,d[N],pos[N],siz[N],a[N];
db k;
struct node
{
ll minn,tag,ls;
}seg[N];
inline node operator +(node x,node y)
{
return node{min(x.minn,y.minn),0ll,x.ls};
}
inline void add(ll pos,ll num)
{
seg[pos].minn+=num;
seg[pos].ls+=num;
seg[pos].tag+=num;
return;
}
inline void pushdown(ll pos)
{
add(pos<<1,seg[pos].tag);
add(pos<<1|1,seg[pos].tag);
seg[pos].tag=0;
return;
}
inline void build(ll pos,ll l,ll r)
{
if(l==r)
seg[pos].minn=seg[pos].ls=n-l+1;
else
{
ll mid=l+r>>1;
build(pos<<1,l,mid);
build(pos<<1|1,mid+1,r);
seg[pos]=seg[pos<<1]+seg[pos<<1|1];
}
return;
}
inline void upgrade(ll pos,ll l,ll r,ll s,ll t,ll num)
{
if(l>=s&&r<=t)
return add(pos,num);
else if(l>t||r<s)
return;
pushdown(pos);
ll mid=l+r>>1;
upgrade(pos<<1,l,mid,s,t,num);
upgrade(pos<<1|1,mid+1,r,s,t,num);
seg[pos]=seg[pos<<1]+seg[pos<<1|1];
return;
}
inline ll query(ll pos,ll l,ll r,ll siz,ll d)
{
if(l==r)
return l;
ll mid=l+r>>1;
pushdown(pos);
if(siz<=min(seg[pos<<1].minn,min(d,seg[pos<<1|1].ls)))
return query(pos<<1|1,mid+1,r,siz,min(d,seg[pos<<1].minn));
return query(pos<<1,l,mid,siz,d);
}
bool vis[N];
map<ll,ll>mp;
signed main()
{
read(n);
cin>>k;
for(int i=1;i<=n;i++)
read(a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
{
if(!mp[a[i]])
mp[a[i]]=i;
}
build(1,1,n);
for(int i=1;i<=n;i++)
siz[i]=1;
for(int i=n;i;i--)
siz[ll(floor(i/k))]+=siz[i];
for(int i=1;i<=n;i++)
{
ll fa=ll(floor(i/k));
if(fa)
upgrade(1,1,n,1,pos[fa],siz[i]);
ll tmp=a[query(1,1,n,siz[i],inf)];
pos[i]=mp[tmp];
mp[tmp]++;
upgrade(1,1,n,1,pos[i],-siz[i]);
}
for(int i=1;i<=n;i++)
printf("%lld ",a[pos[i]]);
exit(0);
}