线段树合并
P1456 Monkey King
链接:https://www.luogu.com.cn/problem/P1456
题意:给定 n 只猴子打 m 次架,每次询问两只猴子,选择它们所在集合中,战斗力最大的两只猴子,将它们的战斗力减半。输出这个减半之后的战斗力,然后将这两个集合合并。如果两只猴子已经在同一个集合,那么就输出 -1.
思路:用并查集维护集合关系。然后用 动态开点权值线段树 + 线段树合并来维护操作。
- 每次先查询 u 、v 是否在同一个集合。不在,则查询当前集合中的最大战斗力的两只猴子,然后删除战斗力,并插入原来战斗力的一半。然后将这两个集合合并。最后查询答案
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,tot=32768;
int n,m;
int root[maxn],st[maxn*40],ls[maxn*40],rs[maxn*40],no;
int query(int rt,int L,int R)
{
if(!rt) return 0;
if(L==R) return L;
int mid=(L+R)>>1;
if(st[rs[rt]]) return query(rs[rt],mid+1,R);
else return query(ls[rt],L,mid);
}
void insert(int &rt,int pos,int L,int R)
{
if(!rt) rt=++no;
st[rt]++;
if(L==R) return;
int mid=(L+R)>>1;
if(pos<=mid) insert(ls[rt],pos,L,mid);
else insert(rs[rt],pos,mid+1,R);
}
void del(int &rt,int pos,int L,int R)
{
if(!rt) return;
st[rt]--;
if(L==R) return;
int mid=(L+R)>>1;
if(pos<=mid) return del(ls[rt],pos,L,mid);
else return del(rs[rt],pos,mid+1,R);
}
int merge(int rt1,int rt2,int L,int R)
{
if(!rt1||!rt2) return rt1?rt1:rt2;
int rt=rt1;
st[rt]+=st[rt2];
int mid=(L+R)>>1;
ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
return rt;
}
int pa[maxn];
int find(int x)
{
return x==pa[x]?x:pa[x]=find(pa[x]);
}
int main()
{
while(~scanf("%d",&n))
{
memset(root,0,sizeof(root));
for(int i=0; i<=no; ++i) st[i]=ls[i]=rs[i]=0;
no=0;
for(int i=1; i<=n; ++i)
{
int x;
scanf("%d",&x);
pa[i]=i;
insert(root[i],x,1,tot);
}
scanf("%d",&m);
int u,v,ru,rv;
while(m--)
{
scanf("%d%d",&u,&v);
ru=find(u);
rv=find(v);
if(ru==rv) puts("-1");
else
{
u=query(root[ru],1,tot);
v=query(root[rv],1,tot);
del(root[ru],u,1,tot);
del(root[rv],v,1,tot);
insert(root[ru],u/2,1,tot);
insert(root[rv],v/2,1,tot);
pa[ru]=rv;
root[rv]=merge(root[ru],root[rv],1,tot);
printf("%d\n",query(root[rv],1,tot));
}
}
}
return 0;
}
P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并
链接:https://www.luogu.com.cn/problem/P4556
题意:首先村落里的一共有 n 座房屋,并形成一个树状结构。然后救济粮分 mm 次发放,每次选择两个房屋 ( x , y ) (x, y) (x,y),然后对于 x 到 y 的路径上(含 x 和 y)每座房子里发放一袋 z 类型的救济粮。当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
思路:树上差分前缀和,维护 sz 和 每个点的最大值。
- 先访问到叶节点,然后差分回溯
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,tot=1e5;
int n,m;
vector<int> e[maxn];
int visit[maxn],ans[maxn];
int root[maxn],ls[maxn*70],rs[maxn*70],no;
struct ST
{
int cnt,sz;
} st[maxn*70];
void update(int &rt,int pos,int L,int R,int val)
{
if(!rt) rt=++no;
if(L==R)
{
st[rt].sz+=val;
st[rt].cnt+=val;
return;
}
int mid=(L+R)>>1;
if(pos<=mid) update(ls[rt],pos,L,mid,val);
else update(rs[rt],pos,mid+1,R,val);
st[rt].sz=st[ls[rt]].sz+st[rs[rt]].sz;
st[rt].cnt=max(st[ls[rt]].cnt,st[rs[rt]].cnt);
}
int depth[maxn],fa[maxn][22];
void dfs1(int u,int f)
{
depth[u]=depth[f]+1;
fa[u][0]=f;
for(int i=1; i<=20; ++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto v: e[u])
{
if(v==f) continue;
dfs1(v,u);
}
}
int lca(int u,int v)
{
if(depth[u]<depth[v]) swap(u,v);
int dis=depth[u]-depth[v];
for(int i=0; (1<<i)<=dis; ++i)
if(dis>>i&1) u=fa[u][i];
if(u==v) return u;
for(int i=20; i>=0; --i)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int merge(int rt1,int rt2,int L,int R)
{
if(!rt1||!rt2) return rt1?rt1:rt2;
int rt=rt1;
if(L==R)
{
st[rt].sz+=st[rt2].sz;
st[rt].cnt+=st[rt2].cnt;
return rt;
}
int mid=(L+R)>>1;
ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
st[rt].sz=st[ls[rt]].sz+st[rs[rt]].sz;
st[rt].cnt=max(st[ls[rt]].cnt,st[rs[rt]].cnt);
return rt;
}
int query(int rt,int L,int R)
{
if(!rt) return 0;
if(L==R) return st[rt].cnt?L:0;
int mid=(L+R)>>1;
if(st[ls[rt]].cnt>=st[rs[rt]].cnt) return query(ls[rt],L,mid);
else return query(rs[rt],mid+1,R);
}
void dfs2(int u,int f)
{
visit[u]=1;
for(auto v: e[u])
{
if(v==f) continue;
dfs2(v,u);
root[u]=merge(root[u],root[v],1,tot);
}
if(root[u]) ans[u]=query(root[u],1,tot);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n-1; ++i)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1,0);
int u,v,w;
while(m--)
{
scanf("%d%d%d",&u,&v,&w);
int x=lca(u,v);
int fx=fa[x][0];
update(root[u],w,1,tot,1);
update(root[v],w,1,tot,1);
update(root[x],w,1,tot,-1);
if(fx) update(root[fx],w,1,tot,-1);
}
dfs2(1,0);
for(int i=1; i<=n; ++i)
printf("%d\n",ans[i]);
return 0;
}
P1552 [APIO2012]派遣
链接:https://www.luogu.com.cn/problem/P1552
题意:给定一棵树,每个点有点权 c i c_i ci 和 l i l_i li ,可以在每棵子树内选择最大的点数使得 c i c_i ci 之和不超过 m m m 。设选择的点数为 c n t cnt cnt ,问 l i × c n t l_i \times cnt li×cnt 的最大值是多少。
思路:
- 对每个点开一棵权值线段树:维护 cnt 和 sum ,表示当前值域区间的个数和区间和
- 从叶到根,先合并当前点子树的所有信息,然后查询即可
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m,tot;
ll C[maxn],L[maxn];
vector<int> allx,e[maxn];
int root[maxn],ls[maxn*40],rs[maxn*40],no;
struct Node
{
int cnt;
ll sum;
} st[maxn*40];
void update(int &rt,int pos,int L,int R)
{
if(!rt) rt=++no;
if(L==R)
{
st[rt].cnt++;
st[rt].sum+=allx[pos-1];
return;
}
int mid=(L+R)>>1;
if(pos<=mid) update(ls[rt],pos,L,mid);
if(pos>mid) update(rs[rt],pos,mid+1,R);
st[rt].cnt=st[ls[rt]].cnt+st[rs[rt]].cnt;
st[rt].sum=st[ls[rt]].sum+st[rs[rt]].sum;
}
int query(int rt,int L,int R,int val)
{
if(!rt) return 0;
if(L==R) return min(st[rt].cnt,val/allx[L-1]);
int mid=(L+R)>>1;
if(st[ls[rt]].sum>=val) return query(ls[rt],L,mid,val);
else return st[ls[rt]].cnt+query(rs[rt],mid+1,R,val-st[ls[rt]].sum);
}
int merge(int rt1,int rt2,int L,int R)
{
if(!rt1||!rt2) return rt1?rt1:rt2;
int rt=rt1;
if(L==R)
{
st[rt].cnt+=st[rt2].cnt;
st[rt].sum+=st[rt2].sum;
return rt;
}
int mid=(L+R)>>1;
ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
st[rt].cnt=st[ls[rt]].cnt+st[rs[rt]].cnt;
st[rt].sum=st[ls[rt]].sum+st[rs[rt]].sum;
return rt;
}
ll ans=0;
void dfs(int u)
{
for(auto v: e[u])
{
dfs(v);
root[u]=merge(root[u],root[v],1,tot);
}
if(root[u]) ans=max(ans,1ll*L[u]*query(root[u],1,tot,m));
}
int main()
{
scanf("%d%d",&n,&m);
int rt;
for(int i=1; i<=n; ++i)
{
int u;
scanf("%d%lld%lld",&u,&C[i],&L[i]);
e[u].push_back(i);
if(u==0) rt=i;
allx.push_back(C[i]);
}
sort(allx.begin(),allx.end());
allx.resize(unique(allx.begin(),allx.end())-allx.begin());
tot=allx.size();
for(int i=1; i<=n; ++i)
{
int pos=lower_bound(allx.begin(),allx.end(),C[i])-allx.begin()+1;
update(root[i],pos,1,tot);
}
dfs(rt);
printf("%lld\n",ans);
return 0;
}
CF1009F Dominant Indices
链接:https://www.luogu.com.cn/problem/CF1009F
题意:给定一棵以 1 为根,n 个节点的树。设 d(u,x) 为 u 子树中到 u 距离为 x 的节点数。 对于每个点,求一个最小的 k,使得 d(u,k) 最大。
思路:
- 每个点根据深度建立权值线段树。查询深度出现次数最多的且最小的k。
- 从叶到根,一遍更新答案一遍向上合并
- 注意一下深度,根节点深度为 1 ,有 n 次更新,空间复杂度为 n l o g 2 ( m a x d e p t h ) n log2(max~depth) nlog2(max depth)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5,tot=1e6;
int n;
vector<int> e[maxn];
int depth[maxn],ans[maxn];
int root[maxn],ls[maxn*25],rs[maxn*25],st[maxn*25],no;
void update(int &rt,int pos,int L,int R)
{
if(!rt) rt=++no;
if(L==R)
{
st[rt]++;
return;
}
int mid=(L+R)>>1;
if(pos<=mid) update(ls[rt],pos,L,mid);
else update(rs[rt],pos,mid+1,R);
st[rt]=max(st[ls[rt]],st[rs[rt]]);
}
int query(int rt,int L,int R)
{
if(!rt) return 0;
if(L==R) return L;
int mid=(L+R)>>1;
if(st[ls[rt]]>=st[rs[rt]]) return query(ls[rt],L,mid);
else return query(rs[rt],mid+1,R);
}
int merge(int rt1,int rt2,int L,int R)
{
if(!rt1||!rt2) return rt1?rt1:rt2;
int rt=rt1;
if(L==R)
{
st[rt]+=st[rt2];
return rt;
}
int mid=(L+R)>>1;
ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
st[rt]=max(st[ls[rt]],st[rs[rt]]);
return rt;
}
void dfs(int u,int fa)
{
depth[u]=depth[fa]+1;
for(auto v: e[u])
{
if(v==fa) continue;
dfs(v,u);
root[u]=merge(root[u],root[v],1,tot);
}
update(root[u],depth[u],1,tot);
ans[u]=query(root[u],1,tot);
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n-1; ++i)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1,0);
for(int i=1; i<=n; ++i)
printf("%d\n",ans[i]-depth[i]);
return 0;
}
P4197 Peaks
链接:https://www.luogu.com.cn/problem/P4197
题意:在 Bytemountains 有 n n n 座山峰,每座山峰有他的高度 h i h_i hi 。有些山峰之间有双向道路相连,共 m 条路径,每条路径有一个困难值 c i c_i ci,这个值越大表示越难走。 现在有 q q q 组询问,每组询问询问从点 v 开始只经过困难值小于等于 x 的路径所能到达的山峰中第 k k k 高的山峰,如果无解输出 − 1 -1 −1
对于 100% 的数据, n ≤ 1 0 5 , 0 ≤ m , q ≤ 5 × 1 0 5 , h i , c , x ≤ 1 0 9 n \le 10^5,0 \le m,q \le 5\times 10^5,h_i,c,x \le 10^9 n≤105,0≤m,q≤5×105,hi,c,x≤109
思路:
- 边根据 w 排序,询问根据 x 排序。然后更新并查集、合并线段树。
- 然后查询第 k 大就好了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=5e5+5;
int n,m,q,tot;
int h[maxn],ans[maxm];
vector<int> allh;
struct Opt
{
int v,x,k,id;
bool operator<(const Opt &b) const
{
return x<b.x;
}
} opt[maxm];
struct Edge
{
int u,v,w;
bool operator<(const Edge& b) const
{
return w<b.w;
}
} e[maxm];
int pa[maxn],sz[maxn];
int find(int x)
{
return x==pa[x]?x:pa[x]=find(pa[x]);
}
int root[maxn],ls[maxn*25],rs[maxn*25],st[maxn*25],no;
void update(int &rt,int pos,int L,int R)
{
if(!rt) rt=++no;
st[rt]++;
if(L==R) return;
int mid=(L+R)>>1;
if(pos<=mid) update(ls[rt],pos,L,mid);
else update(rs[rt],pos,mid+1,R);
}
int merge(int rt1,int rt2,int L,int R)
{
if(!rt1||!rt2) return rt1|rt2;
int rt=rt1;
if(L==R)
{
st[rt]+=st[rt2];
return rt;
}
int mid=(L+R)>>1;
ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
st[rt]=st[ls[rt]]+st[rs[rt]];
return rt;
}
int query(int rt,int k,int L,int R)
{
if(L==R) return L;
int mid=(L+R)>>1;
if(st[rs[rt]]>=k) return query(rs[rt],k,mid+1,R);
else return query(ls[rt],k-st[rs[rt]],L,mid);
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1; i<=n; ++i)
{
scanf("%d",&h[i]);
allh.push_back(h[i]);
pa[i]=i;
sz[i]=1;
}
sort(allh.begin(),allh.end());
allh.resize(unique(allh.begin(),allh.end())-allh.begin());
tot=allh.size();
for(int i=1; i<=n; ++i)
{
int p=lower_bound(allh.begin(),allh.end(),h[i])-allh.begin()+1;
update(root[i],p,1,tot);
}
for(int i=1; i<=m; ++i)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[i]= {u,v,w};
}
sort(e+1,e+1+m);
for(int i=1; i<=q; ++i)
{
int v,x,k;
scanf("%d%d%d",&v,&x,&k);
opt[i]= {v,x,k,i};
}
sort(opt+1,opt+1+q);
int pos=1;
for(int i=1; i<=q; ++i)
{
while(pos<=m&&e[pos].w<=opt[i].x)
{
int u=e[pos].u,v=e[pos].v;
int ru=find(u),rv=find(v);
pos++;
if(ru==rv) continue;
pa[rv]=ru;
sz[ru]+=sz[rv];
root[ru]=merge(root[ru],root[rv],1,tot);
}
int v=opt[i].v;
if(sz[find(v)]<opt[i].k) ans[opt[i].id]=-1;
else
{
int p=query(root[find(v)],opt[i].k,1,tot);
ans[opt[i].id]=allh[p-1];
}
}
for(int i=1; i<=q; ++i)
printf("%d\n",ans[i]);
return 0;
}
P3521 [POI2011]ROT-Tree Rotations
链接:https://www.luogu.com.cn/problem/P3521
题意:给定一棵有 n 个叶节点的二叉树,每个叶节点带有权值 p_i ,构成了一个 n 的排列。保证除了叶节点之外的所有节点左右孩子都存在。现在你可以交换任意节点的左右孩子,交换任意次。问最终得到的叶节点前序遍历得到的排列的最小逆序对是多少。
思路:
- 每一个节点与节点直接是相互独立的。可以对每一个节点开一个权值线段树,在向上合并的过程中统计逆序对。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,tot=2e5;
int n;
int ls[maxn*20],rs[maxn*20],st[maxn*20],no;
void update(int &rt,int pos,int L,int R)
{
if(!rt) rt=++no;
if(L==R)
{
st[rt]++;
return;
}
int mid=(L+R)>>1;
if(pos<=mid) update(ls[rt],pos,L,mid);
else update(rs[rt],pos,mid+1,R);
st[rt]=st[ls[rt]]+st[rs[rt]];
}
ll cnt1,cnt2,ans;
int merge(int rt1,int rt2,int L,int R)
{
if(!rt1||!rt2) return rt1|rt2;
int rt=rt1;
if(L==R)
{
st[rt]+=st[rt2];
return rt;
}
cnt1+=1ll*st[ls[rt1]]*st[rs[rt2]];
cnt2+=1ll*st[rs[rt1]]*st[ls[rt2]];
int mid=(L+R)>>1;
ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
st[rt]=st[ls[rt]]+st[rs[rt]];
return rt;
}
void dfs(int &rt)
{
int u=0,v=0,val;
scanf("%d",&val);
if(val==0)
{
dfs(u);
dfs(v);
cnt1=cnt2=0;
rt=merge(u,v,1,tot);
ans+=min(cnt1,cnt2);
}
else update(rt,val,1,tot);
}
int main()
{
scanf("%d",&n);
int rt=0;
ans=0;
dfs(rt);
printf("%lld\n",ans);
return 0;
}