1. 程式人生 > 其它 >【題解】 P4364 [九省聯考 2018] IIIDX

【題解】 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);
}