[九省联考2018]IIIDX
XXXIV.[九省联考2018]IIIDX
首先,一个非常naive的想法是,建出通关的树出来,然后dfs它,在访问到一个节点时,将现有最小的值赋给它,然后从大到小遍历每个子节点。
这个算法会被 相同的情形叉掉,因为它可以构造出这样一组数据:若某个节点的子树为 ,且它的兄长(指与它有同一父亲,且权值小于它的最大节点)的子树不为 ,且它的值和兄长的值相同。这时,我们可以掏出兄长的一个儿子,将其与该节点交换,并发现树仍然合法,但是字典序变大了。
于是我们开始思考这个问题的本质。
我们发现,用任意一组点集覆盖任意一棵子树,都能保证产生一组解。于是我们萌生了一个念头,能不能从小到大确定每个数的值,且使得剩余的数存在一种合法解?显然这种方法是一定正确的。
我们发现,假如一个点 的子树大小是 ,且赋值为 ,则其子树中每个点的权值都 。这就意味着,对于 的子树,我们需要恰好 个 的节点来填满这棵子树。并且,依照我们上面的发现,这是充要条件。
于是问题转换为找到最大的 使得存在上述点集。
我们考虑用一棵线段树维护,线段树上每个下标 存了 的尚未被动用的权值数。在确定一个点的权值为 后,我们需要对于 的所有位置全都减去 ,因为这其中每个位置都确定可用的权值减少了 。但是这并不意味着 的权值数就不会变化,因为谁又知道子树中到底选了哪些数呢?
因此,在确定一个 时,我们不能简单地找到最大的权值数 的下标,而应找到对于所有 ,都有 的权值数 的最大 。这是很显然的。
而这是线段树二分的常规操作。随便写写就行了。
需要注意的是,确定 时的操作的本质是预订,这就意味着当操作真正进行到儿子时,需要把父亲预订的那些位置给退回,同时注意一个父亲只能被退回一次。
时间复杂度 。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,fa[500100],sz[500100],a[500100],b[500100];
double k;
vector<int>v;
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
struct SegTree{int tag,mn;}seg[2001000];
void pushup(int x){seg[x].mn=min(seg[lson].mn,seg[rson].mn);}
void ADD(int x,int y){seg[x].mn+=y,seg[x].tag+=y;}
void pushdown(int x){ADD(lson,seg[x].tag),ADD(rson,seg[x].tag),seg[x].tag=0;}
void modify(int x,int l,int r,int P,int val){
if(l>P)return;
if(r<=P){ADD(x,val);return;}
pushdown(x),modify(lson,l,mid,P,val),modify(rson,mid+1,r,P,val),pushup(x);
}
int innersearch(int x,int l,int r,int val){
if(l==r)return seg[x].mn>=val?l:l-1;
pushdown(x);
if(seg[lson].mn>=val)return innersearch(rson,mid+1,r,val);
else return innersearch(lson,l,mid,val);
}
int outersearch(int x,int l,int r,int P,int val){
// printf("%d[%d,%d]:%d,%d\n",x,l,r,P,val);
if(r<P)return -1;
if(l>=P){
// printf("[%d,%d]:%d %d\n",l,r,val,seg[x].mn);
if(seg[x].mn>=val)return -1;
return innersearch(x,l,r,val);
}
pushdown(x);
int tmp=outersearch(lson,l,mid,P,val);
if(tmp!=-1)return tmp;
return outersearch(rson,mid+1,r,P,val);
}
bool vis[500100];
void iterate(int x,int l,int r){
if(l==r)printf("%d ",seg[x].mn);
else pushdown(x),iterate(lson,l,mid),iterate(rson,mid+1,r);
}
int main(){
scanf("%d%lf",&n,&k);
for(int i=n,x;i;i--)sz[fa[i]=i/k]+=++sz[i],scanf("%d",&a[i]),v.push_back(a[i]);
sort(v.begin(),v.end()),v.resize(m=unique(v.begin(),v.end())-v.begin());
for(int i=1;i<=n;i++)a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1,modify(1,1,m,a[i],1);
// for(int i=1;i<=n;i++)printf("%d %d\n",i,fa[i]);
// for(int i=1;i<=n;i++)printf("%d ",sz[i]);puts("");
// iterate(1,1,m);puts("");
vis[0]=true,b[0]=1;
for(int i=1;i<=n;i++){
if(!vis[fa[i]])modify(1,1,m,b[fa[i]],sz[fa[i]]-1),vis[fa[i]]=true;
// puts("");
// iterate(1,1,m);puts("");
b[i]=outersearch(1,1,m,b[fa[i]],sz[i]);
if(b[i]==-1)b[i]=m;
// printf("%d:%d\n",sz[i],b[i]);
modify(1,1,m,b[i],-sz[i]);
// iterate(1,1,m);puts("");
}
for(int i=1;i<=n;i++)printf("%d ",v[b[i]-1]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?