題目鏈接:BZOJ 5249
題目大意:n(n<=500000)首曲子,n個難度值d[i] (d[i]<=1000000000)。給定一個實數k (k<=1000000000),完成第
題解:
可以把
考慮貪心。按1~n的順序分配難度,每次確定一個節點的難度時,在能夠滿足子樹內所有節點的難度不小於當前點難度的情況下,最大化當前難度,這樣貪心出來的就會是字典序最大的方案。
實現的時候,先把難度值從大到小排序,對每個位置i,維護res[i]表示它及它的左側尚未被分配的難度值的個數。每次確定一個節點u的難度時,需要找到一個難度值最大且儘可能靠右的位置 pos,滿足res[i]>=siz[u] (pos<=i<=n)
(siz[u]是以u爲根的子樹的大小),d[pos]就是u的難度值了。但確定了u的難度後,還無法確定其子樹各點具體的難度值,所以需要先爲其子樹預留一部分難度值,即res[i]-=siz[u] (pos<=i<=n)
。這就對應着線段樹上二分和區間加減。注意之後再處理u的子樹時要把預留的權值加回去。
具體實現起來還是很巧妙的 (✪ω✪) ~
code(有參考大神的BLOG)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 500005
#define inf 100000000
using namespace std;
inline int read()
{
char c=getchar(); int num=0,f=1;
while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
while (c<='9'&&c>='0') { num=num*10+c-'0'; c=getchar(); }
return num*f;
}
int n,res[N<<2],tag[N<<2],a[N],b[N],ans[N],siz[N],fa[N],cnt[N];
double k;
bool cmp(int a,int b) { return a>b; }
void pushup(int now)
{
res[now]=min(res[now<<1],res[now<<1|1]);
}
void pushdown(int now)
{
tag[now<<1]+=tag[now]; res[now<<1]+=tag[now];
tag[now<<1|1]+=tag[now]; res[now<<1|1]+=tag[now];
tag[now]=0;
}
void build(int now,int l,int r)
{
if (l==r) { res[now]=l; return; }
int mid=l+r>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
pushup(now);
}
int query(int now,int l,int r,int k)
{
if (l==r) return (res[now]>=k)?l:l+1;
int mid=l+r>>1; pushdown(now);
if (k<=res[now<<1|1]) return query(now<<1,l,mid,k);
return query(now<<1|1,mid+1,r,k);
}
void update(int now,int l,int r,int begin,int end,int val)
{
if (begin<=l&&r<=end)
{
res[now]+=val; tag[now]+=val; return;
}
int mid=l+r>>1; pushdown(now);
if (begin<=mid) update(now<<1,l,mid,begin,end,val);
if (end>mid) update(now<<1|1,mid+1,r,begin,end,val);
pushup(now);
}
int main()
{
n=read(); scanf("%lf",&k);
for (int i=1;i<=n;i++) a[i]=read();
sort(a+1,a+n+1,cmp);
for (int i=n-1;i>=1;i--)
if (a[i]==a[i+1]) cnt[i]=cnt[i+1]+1; else cnt[i]=0;
for (int i=1;i<=n;i++) fa[i]=(int)floor(i/k),siz[i]=1;
for (int i=n;i>=1;i--) siz[fa[i]]+=siz[i];
build(1,1,n);
for (int i=1;i<=n;i++)
{
if (fa[i]&&fa[i]!=fa[i-1])
update(1,1,n,ans[fa[i]],n,siz[fa[i]]-1); //加回去
int x=query(1,1,n,siz[i]); //在線段樹上二分
x+=cnt[x]; cnt[x]++; x-=(cnt[x]-1); //使pos儘可能靠右
ans[i]=x;
update(1,1,n,x,n,-siz[i]); //預留權值
}
for (int i=1;i<=n;i++) printf("%d ",a[ans[i]]);
return 0;
}