线段树合并 & 权值线段树 & 一些题目
%%%
权值线段树的一些操作
int getrank(int x){
return query_num(root,1,maxdata,1,x)+1;
}
int getpre(int x){//查询前驱
int tmp=query_num(root,1,maxdata,1,x);
return query_kth(root,1,maxdata,tmp);
}
int getnxt(int x){//查询后继
int tmp=query_num(root,1,maxdata,1,x);
return query_kth(root,1,maxdata,tmp+1);
}
A雨天的尾巴
树上差分,每次操作都单点修改,操作完之后dfs合并线段树。
每个点都开一个权值线段树,维护出现次数最多的权值和出现次数最多的权值的出现次数。
B bzoj4636蒟蒻的序列
区间修改(但又不是完全的区间修改),如果按照提意来去跑叶子节点每次询问的时间复杂度会到。考虑线段树每个节点都维护一个懒标,表示所表示区间内所有数的权值,线段树dfs查询。
update
if(L<=l && r<=R){
tr[rt].lazytag=max(tr[rt].lazytag,k);
return;
}
query
w=max(w,tr[rt].lazytag);
if(!rt){
return 1ll*(R-L+1)*w;
}
C Promotion Counting
这个应该是最裸的板子。树上每一个节点开一个权值线段树维护桶,在树上dfs合并。统计答案的时候每个点都扫一遍,在其对应的线段树上查询比大的数的个数
所以dfs序+分块完全可以做到
D 永无乡
用并查集维护连通块,每一个连通块内建一个权值线段树维护桶。点之间连边就对应所在连通块线段树的合并,然后查询排名就是板子
E 魔法少女Ljj
没啥难的,就是上面几个板子的综合
但是Chano很郁闷,你能帮帮他吗
F大根堆
考虑树形dp。设为以u为根节点的树中选出的最大值不超过的最多的节点,用线段树来维护。
则有
f[u][val]+=f[v][val]//v为u的子节点,此操作对应线段树合并
f[u][val[u]]=max(f[u][val[u]],f[u][val[u]-1]+1);
而且不仅是要更新,所有小于的都要更新,由于本身的是单调的,所以考虑二分查找并且区间更新
涉及到区间更新所以我打了lazy,但是这样可能会开出很多没有用的点来,整一个回收栈,把合并后没有用的点的编号扔到栈里,开新点的时候取出栈里的编号即可(注意扔的时候清空点的信息)
最后答案为
void dfs(int u,int fa){
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
dfs(v,u);
root[u]=merge(root[u],root[v],1,maxdata);
}
int tmp=query(root[u],1,maxdata,val[u]-1)+1;
if(tmp<=query(root[u],1,maxdata,val[u]))return;
int l=val[u],r=maxdata,ans=0;
while(l<=r){
int mid=l+r>>1;
if(query(root[u],1,maxdata,mid)<tmp){
ans=mid;l=mid+1;
}
else r=mid-1;
}
update(root[u],1,maxdata,val[u],ans,1);
}
G 领导集团问题LuoguP4577
和 F 大根堆一样,就是变成了小根堆
H 大融合LuoguP4219
以题面上的图为例,查询<3,8>,先把<3,8>断开,3所在的联通块内有3个点,8所在的联通块内有2个点,从3所在的联通快内任意一个点出发到8所在的联通块内的任意一点,需要经过<3,8>的路径有2*3条,所以查询结果应为6
按照这个思路有一个很显然的做法,从u开始dfs,遇到<u,v>跳过,算出u所在联通块内的点数,v同理。
离线处理,把所有的边都连上,处理出dfs序来,对于每一个联通块开一个权值线段树来维护当前状态下出现的次数(有没有出现)。
查询
如果x是y的父亲,则答案为 联通块内出现总次数(y一侧的点数)与所在联通块内的总点数减去y一侧的点数 (x一侧的点数)的乘积,这样就不用dfs以断开<x,y>
更新
并查集维护联通块再线段树合并即可
G 基站选址
朴素方程
f[i][j]第j个基站建在i最小费用,cost[i][j]第i~j个村庄之间没有被基站i,j覆盖的村庄所需的赔偿费用
f[i][j]=min(f[k][j-1]+cost[k][i])+c[i] k=[j-1,i)
当我们推导 时,我们只考虑了它和前面的基站产生的影响,这时对于最后一个基站我们不会考虑它和之后的村庄产生的影响,则我们可以在最后增加一个村庄
,保证它必定被作为基站(无建设费用)且不对前面产生影响,这样就不会有遗漏的了
++n,++k,w[n]=d[n]=Inf;
第二维只与上一次有关,可以滚掉,在最外层枚举j即可
f[i]=min(f[k]+cost[k][i])+c[i] k=[j-1,i)
难点在于求出
维护两个数组, 点 的左边界(建基站能覆盖到的最靠左的点), 点 的右边界,二分查找即可
对于一个点 如果 没有建基站,那么 就要赔偿 赔偿总费用为 赔偿的费用加上 用线段树区间更新
如果在 处建基站,赔偿费用就为赔偿的费用 不用更新
状态初值 第一个基站建在哪个位置
可能会有多个点的有边界为j,用邻接表维护
int sum=0;//cost[1][j-1];
for(int j=1;j<=n;++j){
f[j]=sum+c[j];
计算如果不在j建基站需要赔偿的费用
for(int p=G.head[j];p;p=G.e[p].next){
int v=G.e[p].to;sum+=w[v];
}
}
ans=f[n];
线段树中维护 为当前决策的点
转移
for(int j=1;j<=n;++j){
if(j-1>=i-1)
f[j]=S.query(root,1,n,i-1,j-1)+c[j];
else f[j]=c[j];
计算如果不在j建基站需要赔偿的费用
for(int p=G.head[j];p;p=G.e[p].next){
int v=G.e[p].to;
if(st[v]>1){
S.update(root,1,n,1,st[v]-1,w[v]);
}
}
}
由于把第二维滚掉了,所以在枚举到新的 时要重新建树把原来的状态覆盖掉
for(int i=1;i<=k;++i){
if(i==1){//初值
···
}
else{
S.build(root,1,n);
···
转移
}
ans=min(ans,f[n]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了