先膜一发xhk大佬,线段树合并之前觉得没什么用,经过xhk大佬讲了一波发现用来处理一些有关树的问题很好用,复杂度也不错(应该是nlog?但是因为常数十分爆炸其实跟nlog^2也差不多。。)
例1:Noip2016 天天爱跑步
https://www.luogu.org/problemnew/show/P1600
将链u->v拆成u->lca和lca->v,然后在u,v打Add标记,在lca和lca父亲分别打Del标记(这是为了防止lca被统计两次),然后从下到上把线段树合并起来即可。
#pragma comment(linker, "/STACK:102400000,102400000")
#include
using namespace std;
const int N=3e5+10;
const int M=20;
void read(int &x)
{
char c=getchar();x=0;
while(!isdigit(c))c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
}
struct SEG{
int ls,rs,w;
}seg[N*40];
int snum,n,m,a[N];
int dep[N],jp[N][M],rt[N],ans[N];
vectormp[N],add[N],del[N];
void dfs1(int pos,int fa,int deep)
{//cerr<=0;i--)
if(dep[jp[x][i]]>=dep[y])x=jp[x][i];
if(x==y)return x;
for(int i=M-1;i>=0;i--)
if(jp[x][i]!=jp[y][i])x=jp[x][i],y=jp[y][i];
return jp[x][0];
}
void ad(int u,int l,int v)
{
add[u].push_back(dep[u]),del[jp[l][0]].push_back(dep[u]);
add[v].push_back(2*dep[l]-dep[u]),del[l].push_back(2*dep[l]-dep[u]);
}
int new_node()
{++snum,seg[snum].ls=seg[snum].rs=seg[snum].w=0;return snum;}
void push_up(int x)
{seg[x].w=seg[seg[x].ls].w+seg[seg[x].rs].w;}
int build(int l,int r,int to)
{
int nw=new_node(),mid;
if(l==r)seg[nw].w++;
else
{
mid=(l+r)>>1;
if(to<=mid)seg[nw].ls=build(l,mid,to);
else seg[nw].rs=build(mid+1,r,to);
push_up(nw);
}
return nw;
}
void ins(int nw,int l,int r,int to)
{
int mid;
if(l==r)seg[nw].w++;
else
{
mid=(l+r)>>1;
if(to<=mid)
{
if(!seg[nw].ls)seg[nw].ls=new_node();
ins(seg[nw].ls,l,mid,to);
}
else
{
if(!seg[nw].rs)seg[nw].rs=new_node();
ins(seg[nw].rs,mid+1,r,to);
}
push_up(nw);
}
}
int mg(int x,int y)
{
if(!x||!y)return x+y;
seg[x].w=seg[x].w+seg[y].w;
if(seg[x].ls||seg[y].ls)seg[x].ls=mg(seg[x].ls,seg[y].ls);
if(seg[x].rs||seg[y].rs)seg[x].rs=mg(seg[x].rs,seg[y].rs);
return x;
}
void Del(int nw,int l,int r,int to)
{
int mid;
if(l==r)seg[nw].w--;
else
{
mid=(l+r)>>1;
if(to<=mid) Del(seg[nw].ls,l,mid,to);
else Del(seg[nw].rs,mid+1,r,to);
push_up(nw);
}
}
int ask(int nw,int l,int r,int to)
{
if(l==r&&to==l)return seg[nw].w;
else
{
int mid=(l+r)>>1;
if(to<=mid&&seg[nw].ls)return ask(seg[nw].ls,l,mid,to);
if(to>mid&&seg[nw].rs)return ask(seg[nw].rs,mid+1,r,to);
return 0;
}
}
void dfs2(int pos,int fa)
{
for(int i=0;i
例2:[USACO18OPEN]Disruption
https://www.luogu.org/problemnew/show/P4374
线段树合并,对于每个点查询该点之下能到dfs序在该点子树外的点的最小边权即可。
ps:这道题线段树合并nlog在1e5数据范围下因为常数巨大跑的反而比树剖+线段树这种nlog^2但常数小的做法慢。。。不过毕竟严格从复杂度来说还是更优的(自我安慰)
#pragma GCC optimize(2)
#include
#define pii pair
#define fi first
#define sc second
using namespace std;
const int N=1e5+10;
const int inf=2e9;
void read(int &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
struct SEG{
int ls,rs,w;
}seg[N*40];
int n,m,ans[N],tofa[N];
int sz[N],dfn[N],tim;
vectormd[N],mp[N];;
int rt[N],snum;
void dfs1(int pos,int fa)
{
int v;
sz[pos]=1,dfn[pos]=++tim;
for(int i=0;i>1;
if(to<=mid)seg[nw].ls=build(l,mid,to,dt);
else seg[nw].rs=build(mid+1,r,to,dt);
push_up(nw);
return nw;
}
void ins(int nw,int l,int r,int to,int dt)
{
if(l==r)
{
seg[nw].w=min(seg[nw].w,dt);
return;
}
int mid=(l+r)>>1;
if(to<=mid)
{
if(!seg[nw].ls)seg[nw].ls=new_node();
ins(seg[nw].ls,l,mid,to,dt);
}
else
{
if(!seg[nw].rs)seg[nw].rs=new_node();
ins(seg[nw].rs,mid+1,r,to,dt);
}
push_up(nw);
}
int mg(int x,int y)
{
if(!x||!y)return x+y;
seg[x].w=min(seg[x].w,seg[y].w);
if(seg[x].ls||seg[y].ls)seg[x].ls=mg(seg[x].ls,seg[y].ls);
if(seg[x].rs||seg[y].rs)seg[x].rs=mg(seg[x].rs,seg[y].rs);
return x;
}
int ask(int nw,int L,int R,int l,int r)
{
if(l>R||r>1,res=2e9;
if(seg[nw].ls)res=min(res,ask(seg[nw].ls,L,R,l,mid));
if(seg[nw].rs)res=min(res,ask(seg[nw].rs,L,R,mid+1,r));
return res;
}
void dfs2(int pos,int fa)
{
pii v;int nw;
for(int i=0;i1)ans[nw]=min(ans[nw],ask(rt[pos],1,dfn[pos]-1,1,tim));
if(dfn[pos]+sz[pos]<=tim)ans[nw]=min(ans[nw],ask(rt[pos],dfn[pos]+sz[pos],tim,1,tim));
}
}
int main()
{
int u,v,w;
// freopen("worry.in","r",stdin);
// freopen("worry.out","w",stdout);
read(n),read(m);
for(int i=1;i1e9)?-1:ans[i]);
}