标签:最大 ret code 大小 upd lan iii const 一个
人类智慧题,我觉得网上的题解大多讲的不清楚(导致我看了很久),我尽量把你讲懂。
首先我观察了一下数据范围,\(d_i\) 互不相同的分有 \(50\%\),先指着这个想一想。可以把 \(i\) 和 \(\lfloor\frac{i}{k}\rfloor\) 连一条边,那么构成的图一定是一颗树,\(0\) 是这棵树的根。
那么我们从 \(0\) 开始遍历,每次把当前点的儿子排个序,优先走编号小的儿子,我们维护一个能用的值域区间,走儿子的时候保证子树能合法的情况下取最大值即可,实际上能得到六十分,关键代码:
sort(g.begin(),g.end());
for(int i=0;i<g.size();i++)
{
int t=siz[g[i]];
ans[g[i]]=a[r-t+1];
dfs(g[i],r-t+1,r);
r-=t;
}
这个方法在 \(d_i\) 不同的时候显然不成立,因为当前点可以会面临很多相同值得选择,而我们不知道给子树划分什么值域区间是最优的,所以就 \(\tt Wa\) 掉了。
我们只能知道子树取值的一个模糊的范围,形如:值大于x的数中要选出y个数
,既然现在确定不了就暂时放一放,但是要把这个限制表示出来。所以我们要给子树预留若干个合法的取值。
这个预留操作看上去就很不可做,实际上是可以维护的。把值从大到小排序,**对于每一个位置都维护一个标记数组 \(f[i]\) **来表示这个预留的限制,\(f[i]\) 就从 \(i\) 往左一个一个选(剩下的在越右边越好),那么加上预留的限制就是把 \(i\geq x\) 的 \(f[i]\) 减去 \(y\),想要求某个位置 \(x\) 之前最多能放的个数就求 \(\min_{i\geq x}f[i]\)
维护标记数组是 \(O(n^2)\) 的,但是你发现所有位置都能共用一个标记数组,观察标记数组的特点可以想到那线段树去维护他,这种方法不只适用此题,对于模糊限制的表示有借鉴意义,我暂且称之为:最小值差分(我觉得比较形象了)
表示出了预留的限制就不难设计本题的算法了,我们按编号大小访问每个点,先把父亲的预留操作回撤(因为已经到子树了),然后在线段树上二分可以找到最左边的 \(x\),这个点的答案就是 \(x\) 了。然后找到和 \(x\) 同权值的最右边的 \(p\)(注意如果 \(p\) 已经被选要继续右移),更新一下标记数组来预留子树的取值。
线段树二分的时候如果右子树的最小值不合法就必须往右子树走,否则就无脑走左子树,但是到最底层的需要判一下当前点合不合法,这是由我们在线段树上的走法决定的,如果不合法需要右移一格。
时间复杂度 \(O(n\log n)\),我的代码是网上广为流传的写法。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 500005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<‘0‘ || c>‘9‘) {if(c==‘-‘) f=-1;}
while(c>=‘0‘ && c<=‘9‘) {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,a[M],cnt[M],siz[M],fa[M],ans[M],vis[M],s[4*M],tag[4*M];
int cmp(int a,int b)
{
return a>b;
}
void up(int i)
{
s[i]=min(s[i<<1],s[i<<1|1]);
}
void down(int i)
{
if(tag[i])
{
s[i<<1]+=tag[i];
tag[i<<1]+=tag[i];
s[i<<1|1]+=tag[i];
tag[i<<1|1]+=tag[i];
tag[i]=0;
}
}
void build(int i,int l,int r)
{
if(l==r)
{
s[i]=l;
return ;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
up(i);
}
void upd(int i,int l,int r,int L,int R,int f)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
s[i]+=f;
tag[i]+=f;
return ;
}
down(i);
int mid=(l+r)>>1;
upd(i<<1,l,mid,L,R,f);
upd(i<<1|1,mid+1,r,L,R,f);
up(i);
}
int ask(int i,int l,int r,int x)
{
if(l==r) return s[i]>=x?l:l+1;
down(i);
int mid=(l+r)>>1;
if(s[i<<1|1]<x) return ask(i<<1|1,mid+1,r,x);
return ask(i<<1,l,mid,x);
}
signed main()
{
n=read();double k;
scanf("%lf",&k);
for(int i=1;i<=n;i++)
{
siz[i]=1;
a[i]=read();
}
sort(a+1,a+n+1,cmp);
build(1,1,n);
for(int i=n;i>=1;i--)
{
int t=i/k;
fa[i]=t;
if(a[i]==a[i+1]) cnt[i]=cnt[i+1]+1;
siz[t]+=siz[i];
}
for(int i=1;i<=n;i++)
{
if(fa[i] && !vis[fa[i]])
{
upd(1,1,n,ans[fa[i]],n,siz[fa[i]]-1);
vis[fa[i]]=1;
}
int x=ask(1,1,n,siz[i]);
x+=cnt[x];cnt[x]++;
ans[i]=x;
upd(1,1,n,x,n,-siz[i]);
}
for(int i=1;i<=n;i++)
printf("%d ",a[ans[i]]);
}
标签:最大 ret code 大小 upd lan iii const 一个
原文地址:https://www.cnblogs.com/C202044zxy/p/14631633.html