LOJ #2472. 「九省联考 2018」IIIDX 贪心+线段树
显然,如果不出现重复数字的话直接贪心填就是正确的.
然而,当出现重复数字时这个贪心就错了.
将这个问题抽象成树是显然的.
我们先将所有数从大到小排.
对于大小为 $size[i]$ 的 $i$ 来说,肯定选当前能选的第 $size[i]$ 大的. (设为 $x$)
那么,选择完 $x$ 后,显然 $x$ 前必须给 $i$ 预留 $size[i]$ 个位置.
那么假设每个点都有 $f[i]$ 表示 $i$ 前最多能填多少个数,我们显然选择满足 $size[i]<=f[j]$ 且最大的 $i$.
这个找 $j$ 的过程可以在线段树上二分,然后找到位置后打上一个标记即可.
code:
#include <bits/stdc++.h>
#define ll long long
#define lson now<<1
#define rson now<<1|1
#define N 500006
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int minn[N<<2],tag[N<<2],size[N];
void build(int l,int r,int now)
{
if(l==r) { minn[now]=l; return; }
int mid=(l+r)>>1;
build(l,mid,lson),build(mid+1,r,rson);
minn[now]=min(minn[lson],minn[rson]);
}
void mark(int x,int v)
{
tag[x]+=v;
minn[x]+=v;
}
void pushdown(int now)
{
if(tag[now])
{
mark(lson,tag[now]);
mark(rson,tag[now]);
tag[now]=0;
}
}
void update(int l,int r,int now,int L,int R,int v)
{
if(l>=L&&r<=R)
{
mark(now,v);
return;
}
pushdown(now);
int mid=(l+r)>>1;
if(L<=mid)
update(l,mid,lson,L,R,v);
if(R>mid)
update(mid+1,r,rson,L,R,v);
minn[now]=min(minn[lson],minn[rson]);
}
int query(int l,int r,int now,int k)
{
if(l==r) return minn[now]<k?(l+1):l;
int mid=(l+r)>>1;
pushdown(now);
if(minn[rson]>=k)
return query(l,mid,lson,k);
else
return query(mid+1,r,rson,k);
}
int w[N],val[N],nxt[N],fa[N],clr[N],ans[N];
bool cmp(int a,int b) { return a>b; }
int main()
{
// setIO("input");
int i,j,n;
double tmp;
scanf("%d%lf",&n,&tmp);
for(i=1;i<=n;++i)
scanf("%d",&w[i]);
sort(w+1,w+1+n,cmp);
for(i=n;i>=1;--i)
{
++size[i];
nxt[i]=i;
fa[i]=(int)(1.0*i/tmp);
size[fa[i]]+=size[i];
if(i!=n&&w[i]==w[i+1])
nxt[i]=nxt[i+1];
}
build(1,n,1);
for(i=1;i<=n;++i)
{
if(fa[i]&&!clr[fa[i]])
{
update(1,n,1,ans[fa[i]],n,size[fa[i]]-1);
clr[fa[i]]=1;
}
ans[i]=query(1,n,1,size[i]);
ans[i]=nxt[ans[i]];
update(1,n,1,ans[i],n,-size[i]);
}
for(i=1;i<=n;++i)
printf("%d ",w[ans[i]]);
return 0;
}

浙公网安备 33010602011771号