树链剖分习题集
DAY4树链剖分 题解
LCA
这种题肯定是一眼差分题。
那么问题变为:给定
显然可以离线,借助充分利用历史答案的思想,将右端点递增排序。
然后考虑怎么维护。这里需要用到
考虑单求
暴力的向上跳链不仅仅可以自底向上,也可以自上而下,也即将
这启发我们:插入一个点
一次询问
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 500050
#define ll long long
const ll p=201314;
struct node{
int l,r;
ll sum,lz;
}t[N<<2];
struct Node{
#define lc x<<1
#define rc x<<1|1
inline void pushup(int x){
t[x].sum=t[lc].sum+t[rc].sum;
t[x].sum%=p;
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r,t[x].sum=t[x].lz=0;
if(l==r)return ;
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
}
void pushdown(node &a,node &b,node &c){
b.lz+=a.lz,c.lz+=a.lz;
b.sum+=(b.r-b.l+1)*a.lz;
c.sum+=(c.r-c.l+1)*a.lz;
b.lz%=p,c.lz%=p,b.sum%=p,c.sum%=p;
a.lz=0;
}
void pushdown(int x){
if(t[x].lz==0)return ;
pushdown(t[x],t[lc],t[rc]);
}
void change(int x,int l,int r,ll k){
if(l<=t[x].l&&t[x].r<=r){
t[x].lz+=k,t[x].sum+=k*(t[x].r-t[x].l+1);
t[x].lz%=p,t[x].sum%=p;
return ;
}
pushdown(x);
if(l<=t[lc].r)change(lc,l,r,k);
if(t[lc].r<r)change(rc,l,r,k);
pushup(x);
}
ll find(int x,int l,int r){
if(l<=t[x].l&&t[x].r<=r)return t[x].sum;
ll ans=0;
pushdown(x);
if(l<=t[lc].r)ans+=find(lc,l,r);
if(t[lc].r<r)ans+=find(rc,l,r);
return ans%p;
}
}s;
struct ask{
int id,x,u,tag;
bool operator<(const ask b){
return x<b.x;
}
}ask[N];
int ans[N],dfn[N],num,top[N],son[N],siz[N],n,m,tot,head[N],ver[N],nxt[N],f[N],rt,cnt,dep[N];
void add(int u,int v){
nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs1(int u,int fa){
siz[u]=1,dep[u]=dep[f[u]]+1;int len=-1;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(len<siz[v])son[u]=v,len=siz[v];
}
}
void dfs2(int u,int tp){
top[u]=tp;dfn[u]=++num;
if(!son[u])return ;
dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==son[u]||v==f[u])continue;
dfs2(v,v);
}
}
void init(){
cin>>n>>m;dep[1]=1;
for(int i=2;i<=n;i++){
int u;cin>>u;++u;
f[i]=u;add(u,i);add(i,u);
}
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=m;i++){
int l,r,u;cin>>l>>r>>u;
l++,r++,u++;
ask[++cnt].id=i,ask[cnt].tag=-1,ask[cnt].x=l-1,ask[cnt].u=u;
ask[++cnt].id=i,ask[cnt].tag= 1,ask[cnt].x=r ,ask[cnt].u=u;
}
sort(ask+1,ask+cnt+1);
s.build(1,1,n);
}
void updata(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
s.change(1,dfn[top[v]],dfn[v],k);
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
s.change(1,dfn[u],dfn[v],k);
}
ll query(int u,int v){
ll ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
ans+=s.find(1,dfn[top[v]],dfn[v]);
ans%=p;
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
ans+=s.find(1,dfn[u],dfn[v]);
return ans%p;
}
void solve(){
int q=0;
for(int i=1;i<=cnt;i++){
while(q<ask[i].x)updata(1,++q,1);
ans[ask[i].id]+=ask[i].tag*query(1,ask[i].u);
}
for(int i=1;i<=m;i++)cout<<(ans[i]%p+p)%p<<endl;
}
int main(){
ios::sync_with_stdio(false);
init();solve();
return 0;
}
骑士的旅行
乱搞。
类比树剖思路,可以对于线段树上每一个节点
合并显然:
struct node{
int res[22];
};
struct tree{
node res;
int l,r;
}t[N<<2];
struct Node{
#define lc x<<1
#define rc x<<1|1
node pushup(node a,node b){
int c[45],tot=0;
node res;memset(res.res,0,sizeof res.res);
for(int i=1;i<=k;i++){
c[++tot]=a.res[i],c[++tot]=b.res[i];
}
sort(c+1,c+tot+1,cmp);
for(int i=1;i<=k;i++)res.res[i]=c[i];
return res;
}
然后其他是常规操作
对于变迁操作,可以看做一删一加,可以合并起来写
void updata(int x,int pos,int xx,int yy){
if(t[x].l==t[x].r){
if(xx!=0)a[rew[pos]].erase(a[rew[pos]].find(xx));
if(yy!=0)a[rew[pos]].insert(yy);
memset(t[x].res.res,0,sizeof t[x].res.res);
int cnt=0;
for(auto it=a[rew[pos]].begin();it!=a[rew[pos]].end()&&cnt<k;it++)
t[x].res.res[++cnt]=*it;
return;
}
if(t[lc].r>=pos)updata(lc,pos,xx,yy);
else updata(rc,pos,xx,yy);
t[x].res=pushup(t[lc].res,t[rc].res);
}
剩下的是板子了。
注意数组清零
遥远的国度
线段树维护区间最小值,支持区间赋值
然后对于一个查询分类讨论,设当前根为
若
若
若dfn[y]~dfn[y]+siz[y]-1
都是不能被统计的,求两边取min
即可
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define N 500050
struct node{
int mn,l,r,lz;
}t[N<<2];
int tt;
int head[N],ver[N<<1],nxt[N<<1],tot,num,top[N],dfn[N],rew[N],p,siz[N],son[N],dep[N],f[N][25],n,m,a[N],rt;
struct Node{
#define lc x<<1
#define rc x<<1|1
void pushup(int x){
t[x].mn=min(t[lc].mn,t[rc].mn);
}
void pushdown(node &a,node &b,node &c){
b.lz=c.lz=a.lz;
b.mn=c.mn=a.lz;
a.lz=0;
}
void pushdown(int x){
if(t[x].lz)pushdown(t[x],t[lc],t[rc]);
}
void change(int x,int l,int r,int k){
if(l<=t[x].l&&t[x].r<=r){
t[x].lz=t[x].mn=k;
return ;
}
pushdown(x);
if(t[lc].r>=l)change(lc,l,r,k);
if(t[rc].l<=r)change(rc,l,r,k);
pushup(x);
}
int find(int x,int l,int r){
if(l<=t[x].l&&t[x].r<=r){
return t[x].mn;
}
pushdown(x);
int res=0x7fffffff;
if(t[lc].r>=l)res=min(res,find(lc,l,r));
if(t[rc].l<=r)res=min(res,find(rc,l,r));
return res;
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r,t[x].lz=0;
if(l==r){
t[x].mn=a[rew[l]];
return ;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(x);
}
}s;
void dfs1(int u,int fa){
dep[u]=dep[fa]+1,siz[u]=1,f[u][0]=fa;
for(int i=1;i<=tt;i++)f[u][i]=f[f[u][i-1]][i-1];
int len=-1;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>len)len=siz[v],son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++num,rew[num]=u;
if(!son[u])return ;
dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==f[u][0]||v==son[u])continue;
dfs2(v,v);
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
v=f[top[v]][0];
}
if(dep[u]>dep[v])swap(u,v);
return u;
}
void add(int u,int v){
nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void init(){
cin>>n>>m;tt=log(n)/log(2)+1;
for(int i=2;i<=n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)cin>>a[i];
dfs1(1,0);dfs2(1,1);
s.build(1,1,n);
cin>>rt;
}
void change(int u,int v,int k){
int p=0;
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
s.change(1,dfn[top[v]],dfn[v],k);
v=f[top[v]][0];
}
if(dep[u]>dep[v])swap(u,v);
s.change(1,dfn[u],dfn[v],k);
}
void solve(){
while(m--){
int opt,u,v;cin>>opt;
if(opt==1)cin>>rt;
if(opt==2){
int u,v,w;cin>>u>>v>>w;
change(u,v,w);
}
if(opt==3){
cin>>u;
int lca=LCA(u,rt);
if(rt==u){
cout<<s.find(1,1,n)<<"\n";
}
else if(lca==u){
int v=rt,k=dep[rt]-dep[u]-1;
for(int i=tt;i>=0;i--){
if((k>>i)&1)v=f[v][i];
}
int ans=s.find(1,1,dfn[v]-1);
if(dfn[v]+siz[v]<=n){
ans=min(ans,s.find(1,dfn[v]+siz[v],n));
}
cout<<ans<<"\n";
}
else {
cout<<s.find(1,dfn[u],dfn[u]+siz[u]-1)<<"\n";
}
}
}
}
int main(){
// freopen("P3979_6.in","r",stdin);
// freopen("P3979_6.ans","w",stdout);
ios::sync_with_stdio(false);
init();solve();
return 0;
}
/*
3 7
1 2
1 3
1 2 3
1
3 1
2 1 1 6
3 1
2 2 2 5
3 1
2 3 3 4
3 1
*/
//fc C:\Users\HF01\Desktop\P3979_6.ans C:\Users\HF01\Desktop\P3979_6.out
动态树
由于有去重,维护多个标记又很烦,所以开始乱搞。
考虑——将每一个需要加上的区间存下来(
然后将其离散化,运用差分思想将其差分,然后求其前缀和
对于大于1的位置重置为1,再处理出连续的为1区间即可。容易发现这样做区间个数也只有
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define int long long
#define p (1ll<<31)
#define N 200500
struct node{
int l,r; ll sum,lz;
}t[N<<2];
int dfn[N],rew[N],head[N],ver[N<<1],nxt[N<<1],f[N],dep[N],son[N],siz[N],top[N],a[N],b[N],c[N],n,m,tot,cnt,num;
int tot2;
struct ask{
int l,r;
bool operator<(const ask b){
return r==b.r?l<b.l:r<b.r;
}
}ask[N],g[N];
int u1[N],v1[N];
struct Node{
#define lc x<<1
#define rc x<<1|1
void pushup(int x){
t[x].sum=t[lc].sum+t[rc].sum;
t[x].sum%=p;
}
void pushdown(node &a,node &b,node &c){
b.lz+=a.lz,c.lz+=a.lz;
b.sum+=(b.r-b.l+1)*a.lz;
c.sum+=(c.r-c.l+1)*a.lz;
a.lz=0;
b.sum%=p,c.sum%=p;
b.lz%=p,c.lz%=p;
}
void pushdown(int x){
if(t[x].lz)pushdown(t[x],t[lc],t[rc]);
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r,t[x].lz=0;
if(l==r){
t[x].sum=a[rew[l]];
return ;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(x);
}
void updata(int x,int pos,ll k){
if(t[x].l==t[x].r){
t[x].sum+=k;
t[x].sum%=p;
return ;
}
pushdown(x);
if(t[lc].r>=pos)updata(lc,pos,k);
else updata(rc,pos,k);
pushup(x);
}
ll find(int x,int l,int r){
if(l<=t[x].l&&t[x].r<=r)return t[x].sum;
pushdown(x);
ll ans=0;
if(l<=t[lc].r)ans+=find(lc,l,r);
if(t[rc].l<=r)ans+=find(rc,l,r);
pushup(x);
return ans;
}
void change(int x,int l,int r,ll k){
if(l<=t[x].l&&t[x].r<=r){
t[x].lz+=k;t[x].sum+=(t[x].r-t[x].l+1)*k;
t[x].lz%=p;t[x].sum%=p;
return ;
}
pushdown(x);
if(l<=t[lc].r)change(lc,l,r,k);
if(t[rc].l<=r)change(rc,l,r,k);
pushup(x);
}
}s;
void add(int u,int v){
nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs1(int u,int fa){
dep[u]=dep[fa]+1,siz[u]=1,f[u]=fa;
int len=-1;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>len)len=siz[v],son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++num,rew[num]=u;
if(!son[u])return ;
dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==f[u]||v==son[u])continue;
dfs2(v,v);
}
}
void find(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
ask[++tot2].l=dfn[top[v]],ask[tot2].r=dfn[v];
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
ask[++tot2].l=dfn[u],ask[tot2].r=dfn[v];
}
signed main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=2;i<=n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,1);
s.build(1,1,n);
cin>>m;
while(m--){
int opt;cin>>opt;tot2=0;
if(opt==0){
int u,k;cin>>u>>k;
s.change(1,dfn[u],dfn[u]+siz[u]-1,k);
continue;
}
int k;cin>>k;
int ans=0;
for(int i=1;i<=k;i++)cin>>u1[i]>>v1[i];
for(int i=1;i<=k;i++)find(u1[i],v1[i]);
int cnt=0;
for(int i=1;i<=tot2;i++)b[++cnt]=ask[i].l,b[++cnt]=ask[i].r,b[++cnt]=ask[i].r+1;
sort(b+1,b+cnt+1);
cnt=unique(b+1,b+cnt+1)-b-1;
for(int i=0;i<cnt+3;i++)c[i]=0;
for(int i=1;i<=tot2;i++){
int u=lower_bound(b+1,b+cnt+1,ask[i].l)-b;
int v=lower_bound(b+1,b+cnt+1,ask[i].r)-b;
c[u]++,c[v+1]--;
}
for(int i=1;i<=cnt;i++)c[i]+=c[i-1];
for(int i=1;i<=cnt+1;i++)c[i]=(c[i]>=1);
int l=1,r=0,tag=0;
for(int i=1;i<=cnt+1;i++){
if(c[i]==0&&c[i-1]==1){
g[++tag].l=b[l],g[tag].r=b[i-1];
}
if(c[i]==1&&c[i-1]==0)l=i;
}
for(int i=1;i<=tag;i++){
ans+=s.find(1,g[i].l,g[i].r);
ans%=p;
}
cout<<ans%p<<"\n";
}
}
运输计划
显然,虫洞应该建立在最长路径上的某一条边,现将边权下放点权
所以,考虑枚举这条边,问题变为优化查询删掉这条边后的最长路径
设(u,v,w),id=k
,考虑它能更新什么
显然,可以将(u,v)
上的k-1
取min
,这样不会影响答案——为什么?
因为,如果这条路径和k-1
有重合,重合部分会标记为k-2
,即使与k-2
有重合也会标为k-3
……以此类推,正确性显然。
维护区间最小值,编号从小到大加入,就会使得每个叶子最多更新一次,均摊时间复杂度是
然后枚举这条边,单点查即可。
#include<iostream>
#include<algorithm>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
#define N 355000
struct Ask{
int u,v,w,lca;
}ask[N];
bool cmp(Ask a,Ask b){
return a.w>b.w;
}
int n,m,sum[N],mxw,ans=inf,head[N],LcA,ver[N<<1],nxt[N<<1],tot,cost[N<<1],a[N],dep[N],siz[N],top[N],f[N],son[N],dfn[N],rew[N],num;
void add(int u,int v,int w){
nxt[++tot]=head[u],ver[head[u]=tot]=v,cost[tot]=w;
}
struct node{
int tag,mx,lz,l,r;
}t[N<<2];
struct Node{
#define lc x<<1
#define rc x<<1|1
void pushup(int x){
t[x].tag=t[lc].tag|t[rc].tag;
if(t[lc].mx!=t[rc].mx)t[x].tag=1;
t[x].mx=max(t[lc].mx,t[rc].mx);
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r;
if(l==r)return ;
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
}
void pushdown(int x){
t[lc].lz=t[rc].lz=t[x].lz;
t[lc].mx=t[rc].mx=t[x].lz;
t[x].lz=0;
}
void update(int x,int l,int r,int k){
if(l<=t[x].l&&t[x].r<=r&&t[x].tag==0){
t[x].mx=t[x].lz=k+1;
return;
}
if(t[x].lz)pushdown(x);
if(t[lc].r>=l&&t[lc].mx>=k)update(lc,l,r,k);
if(t[rc].l<=r&&t[rc].mx>=k)update(rc,l,r,k);
pushup(x);
}
int find(int x,int pos){
if(t[x].l==t[x].r)return t[x].mx;
if(t[x].lz)pushdown(x);
if(t[lc].r>=pos) return find(lc,pos);
return find(rc,pos);
}
}s;
void dfs1(int u,int fa){
f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
int len=-1;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs1(v,u);
a[v]=cost[i],siz[u]+=siz[v];
if(len<siz[v])len=siz[v],son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++num,rew[num]=u;
if(!son[u])return ;
dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==f[u]||v==son[u])continue;
dfs2(v,v);
}
}
int query(int u,int v){
int ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
ans+=sum[dfn[v]]-sum[dfn[top[v]]-1];
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
ans+=sum[dfn[v]]-sum[dfn[u]];
LcA=u;
return ans;
}
void change(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
s.update(1,dfn[top[u]],dfn[u],k);
u=f[top[u]];
}
if(dep[u]>dep[v])swap(u,v);
s.update(1,dfn[u]+1,dfn[v],k);
}
int main(){
// ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
mxw=max(mxw,w);
}
dfs1(1,0);
dfs2(1,1);
s.build(1,1,n);
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[rew[i]];
for(int i=1;i<=m;i++){
cin>>ask[i].u>>ask[i].v;
ask[i].w=query(ask[i].u,ask[i].v);
ask[i].lca=LcA;
}
sort(ask+1,ask+m+1,cmp);
for(int i=1;i<=m;i++){
if(ask[i].w<=ask[1].w-mxw) break;
change(ask[i].u,ask[i].v,i-1);
}
int u=ask[1].u,v=ask[1].v,lca=ask[1].lca;
while(u!=lca)ans=min(ans,max(ask[1].w-a[u],ask[s.find(1,dfn[u])+1].w)),u=f[u];
while(v!=lca)ans=min(ans,max(ask[1].w-a[v],ask[s.find(1,dfn[v])+1].w)),v=f[v];
if(ans==inf)ans=0;
cout<<ans<<"\n";
return 0;
}
染色
线段树维护颜色块数量就不多说了,树剖向上合并的时候和线段树合并规则是一样的。
代码在合并的时候细节有点多,标注在里面了。
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 500050
struct node{
int l,r,lx,rx,cnt,lz;
}t[N<<1];
node merge(node b,node c){
if(b.lx==0&&b.rx==0)return c;
if(c.lx==0&&c.rx==0)return b;
node ans;
ans.lx=(b.lx?b.lx:c.lx),ans.rx=(c.rx?c.rx:b.rx);
ans.cnt=b.cnt+c.cnt-(b.rx==c.lx);
return ans;
}
int dfn[N],head[N],ver[N<<1],nxt[N<<1],tot,num,n,m;
int son[N],f[N],top[N],dep[N],rew[N],val[N],siz[N];
struct Node{
#define lc x<<1
#define rc x<<1|1
void pushup(node &a,node &b,node &c){
a.cnt=b.cnt+c.cnt-(b.rx==c.lx);
a.lx=b.lx,a.rx=c.rx;
}
void pushup(int x){
pushup(t[x],t[lc],t[rc]);
}
void pushdown(node &a,node &b,node &c){
b.lz=a.lz,c.lz=a.lz;
b.lx=b.rx=c.lx=c.rx=a.lz;
b.cnt=1,c.cnt=1;
a.lz=0;
}
void pushdown(int x){
if(!t[x].lz)return ;
pushdown(t[x],t[lc],t[rc]);
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r,t[x].lz=0;
if(l==r){
t[x].lx=t[x].rx=val[rew[l]];
t[x].cnt=1;
return ;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(x);
}
void change(int x,int l,int r,int k){
if(l<=t[x].l&&t[x].r<=r){
t[x].cnt=1,t[x].lx=t[x].rx=k;
t[x].lz=k;
return ;
}
pushdown(x);
if(l<=t[lc].r)change(lc,l,r,k);
if(t[rc].l<=r)change(rc,l,r,k);
pushup(x);
}
node find(int x,int l,int r){
if(l<=t[x].l&&t[x].r<=r)return t[x];
node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
s2=s1;
pushdown(x);
if(l<=t[lc].r)s1=find(lc,l,r);
if(t[rc].l<=r)s2=find(rc,l,r);
pushup(x);return merge(s1,s2);
}
}s;
void add(int u,int v){
nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs1(int u,int fa){
f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
int len=-1;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>len)son[u]=v,len=siz[v];
}
}
void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++num,rew[num]=u;
if(!son[u])return ;
dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==f[u]||v==son[u])continue;
dfs2(v,v);
}
}
void init(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>val[i];
for(int i=2;i<=n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,1);
s.build(1,1,n);
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
return u;
}
void change(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
s.change(1,dfn[top[v]],dfn[v],k);
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
s.change(1,dfn[u],dfn[v],k);
}
int query(int u,int v){
int k=LCA(u,v);
node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
s2=s1;
while(top[u]!=top[k]){
s1=merge(s.find(1,dfn[top[u]],dfn[u]),s1);//由于线段树的关系,只能以下面的链为右儿子
u=f[top[u]];
}
s1=merge(s.find(1,dfn[k],dfn[u]),s1);
swap(s1.lx,s1.rx);//但事实上需要u->LCA(u,v)的链是以LCA(u,v)为右端点的(判断合并),故翻转一下区间
while(top[v]!=top[k]){
s2=merge(s.find(1,dfn[top[v]],dfn[v]),s2);
v=f[top[v]];
}
s2=merge(s.find(1,dfn[k],dfn[v]),s2);
return merge(s1,s2).cnt;
}
void solve(){
while(m--){
char x;int u,v,k;
cin>>x;
if(x=='Q'){
cin>>u>>v;
cout<<query(u,v)<<"\n";
}
else {
cin>>u>>v>>k;
change(u,v,k);
}
}
}
int main(){
ios::sync_with_stdio(false);
init();solve();
return 0;
}
轻重边
NOI真题,典中典。
首先问题在于一类操作,不可能真的修改轻重边,按照树剖的套路,肯定只能修改这条链。那么问题来了,在只能修改链的情况下,如何判断一条边
如果对染色很有灵感的同学,应该可以想到:每一次都给路径上的点染上一种与众不同的颜色,那么
这就好办了,最初给所有点分别染上颜色
至于线段树维护和树剖的维护,都是比区间合并部分的两端点颜色是否相同即可。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define N 550005
struct node{
int l,r,lx,rx,cnt,lz;
}t[N<<2];
inline node merge(node b,node c){
if(b.lx==0&&b.rx==0)return c;
if(c.lx==0&&c.rx==0)return b;
node ans;
ans.lx=(b.lx?b.lx:c.lx),ans.rx=(c.rx?c.rx:b.rx);
ans.cnt=b.cnt+c.cnt+(b.rx==c.lx);
return ans;
}
int dfn[N],head[N],ver[N<<1],nxt[N<<1],tot,num,n,m,color;
int son[N],f[N],top[N],dep[N],rew[N],val[N],siz[N];
struct Node{
#define lc x<<1
#define rc x<<1|1
inline void pushup(node &a,node &b,node &c){
a.cnt=b.cnt+c.cnt+(b.rx==c.lx);
a.lx=b.lx,a.rx=c.rx;
}
inline void pushup(int x){
pushup(t[x],t[lc],t[rc]);
}
inline void pushdown(node &a,node &b,node &c){
b.lz=a.lz,c.lz=a.lz;
b.lx=b.rx=c.lx=c.rx=a.lz;
b.cnt=b.r-b.l,c.cnt=c.r-c.l;
a.lz=0;
}
inline void pushdown(int x){
if(!t[x].lz)return ;
pushdown(t[x],t[lc],t[rc]);
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r,t[x].lz=0;
if(l==r){
t[x].lx=t[x].rx=val[rew[l]];
t[x].cnt=0;
return ;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(x);
}
void change(int x,int l,int r,int k){
if(l<=t[x].l&&t[x].r<=r){
t[x].cnt=t[x].r-t[x].l,t[x].lx=t[x].rx=k;
t[x].lz=k;
return ;
}
pushdown(x);
if(l<=t[lc].r)change(lc,l,r,k);
if(t[rc].l<=r)change(rc,l,r,k);
pushup(x);
}
node find(int x,int l,int r){
if(l<=t[x].l&&t[x].r<=r)return t[x];
node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
s2=s1;
pushdown(x);
if(l<=t[lc].r)s1=find(lc,l,r);
if(t[rc].l<=r)s2=find(rc,l,r);
pushup(x);return merge(s1,s2);
}
}s;
inline void add(int u,int v){
nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
inline void dfs1(int u,int fa){
f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
int len=-1;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>len)son[u]=v,len=siz[v];
}
}
inline void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++num,rew[num]=u;
if(!son[u])return ;
dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==f[u]||v==son[u])continue;
dfs2(v,v);
}
}
inline void init(){
cin>>n>>m;
for(int i=1;i<=n;i++)val[i]=i;
color=n;
for(int i=2;i<=n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,1);
s.build(1,1,n);
}
inline int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
return u;
}
inline void change(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
s.change(1,dfn[top[v]],dfn[v],k);
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
s.change(1,dfn[u],dfn[v],k);
}
inline int query(int u,int v){
int k=LCA(u,v);
node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
s2=s1;
while(top[u]!=top[k]){
s1=merge(s.find(1,dfn[top[u]],dfn[u]),s1);
u=f[top[u]];
}
s1=merge(s.find(1,dfn[k],dfn[u]),s1);
swap(s1.lx,s1.rx);
while(top[v]!=top[k]){
s2=merge(s.find(1,dfn[top[v]],dfn[v]),s2);
v=f[top[v]];
}
s2=merge(s.find(1,dfn[k],dfn[v]),s2);
return merge(s1,s2).cnt;
}
inline void solve(){
while(m--){
int opt,u,v;
cin>>opt>>u>>v;
if(opt==1){
change(u,v,++color);
}
else cout<<query(u,v)-1<<"\n";
}
}
inline void clear(){
for(int i=1;i<=n;i++){
val[i]=dep[i]=son[i]=siz[i]=head[i]=dep[i]=0;
}
color=tot=num=0;
}
int main(){
ios::sync_with_stdio(false);
int T;cin>>T;
while(T--){clear();init();solve();}
return 0;
}
旅游
形式化题意:每次询问支持两个操作
- 给定路径
,支持给路径上所有点权值加上 。 - 给定路径
,求
考虑简化版问题:给定序列
这有点像最小子段和的求法——如果我们把
那么类比最小子段和,不难想到开一棵线段树维护:
维护区间最大值 维护区间最小值 维护答案
考虑维护更新
这就在
我们尝试将其扩展到树上,如果仅仅需要考虑
为了维护这条链,在区间上讲就是答案就是从右到左的差值最大值变成了从左到右的差值最大值而已,不妨更改一下维护树剖的线段树定义:
表示从左到右的答案 表示从右到左的答案
容易得到:
对于树剖各个重链答案合并的问题:将下面的链视作右儿子,上跳到的链视为左儿子,当前答案是父亲来搞就行。
最后思考获取答案的问题:
设
线段树维护即可。
还有一个区间加的懒标记下传问题:事实上
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 500500
struct node{
int l,r,lx,rx,mn,mx,lz;
}t[N<<2];
int n,m,dep[N],head[N],ver[N<<1],nxt[N<<1],val[N],siz[N],dfn[N],num,tot,f[N],son[N],top[N],rew[N];
void pushup(node &a,node &b,node &c){
a.mn=min(b.mn,c.mn);a.mx=max(b.mx,c.mx);
a.lx=max(b.lx,c.lx),a.rx=max(b.rx,c.rx);
a.lx=max(a.lx,b.mx-c.mn);
a.rx=max(a.rx,c.mx-b.mn);
}
struct Node{
#define lc x<<1
#define rc x<<1|1
void pushup(node &a,node &b,node &c){
a.mn=min(b.mn,c.mn);a.mx=max(b.mx,c.mx);
a.lx=max(b.lx,c.lx),a.rx=max(b.rx,c.rx);
a.lx=max(a.lx,b.mx-c.mn);
a.rx=max(a.rx,c.mx-b.mn);
}
void pushdown(node &a,node &b,node &c){
b.lz+=a.lz,c.lz+=a.lz;
b.mn+=a.lz,b.mx+=a.lz;
c.mn+=a.lz,c.mx+=a.lz;
a.lz=0;
}
void pushup(int x){
pushup(t[x],t[lc],t[rc]);
}
void pushdown(int x){
if(!t[x].lz)return ;
pushdown(t[x],t[lc],t[rc]);
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r,t[x].lz=0;
if(l==r){
t[x].mn=t[x].mx=val[rew[l]];
t[x].lx=t[x].rx=0;
return ;
}
int mid=l+r>>1;
build(lc,l,mid),build(rc,mid+1,r);
pushup(x);
}
void change(int x,int l,int r,int k){
if(l<=t[x].l&&t[x].r<=r){
t[x].mn+=k,t[x].mx+=k;
t[x].lz+=k;
return ;
}
pushdown(x);
if(l<=t[lc].r)change(lc,l,r,k);
if(t[rc].l<=r)change(rc,l,r,k);
pushup(x);
}
node find(int x,int l,int r){
if(l<=t[x].l&&t[x].r<=r)return t[x];
pushdown(x);
node s1,s2,ans;s1.lx=s1.rx=0,
s1.mn=1e9,s1.mx=-1e9,s2=s1;
if(l<=t[lc].r)s1=find(lc,l,r);
if(t[rc].l<=r)s2=find(rc,l,r);
pushup(ans,s1,s2);
return ans;
}
}s;
void dfs1(int u,int fa){
f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
int len=-1;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>len)son[u]=v,len=siz[v];
}
}
void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++num,rew[num]=u;
if(!son[u])return ;
dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==son[u]||v==f[u])continue;
dfs2(v,v);
}
}
void change(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
s.change(1,dfn[top[v]],dfn[v],k);
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
s.change(1,dfn[u],dfn[v],k);
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
v=f[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
return u;
}
int query(int u,int v){
int k=LCA(u,v);
node s1,s2,s3,s4;s1.lx=s1.rx=0,
s1.mn=1e9,s1.mx=-1e9,s2=s3=s4=s1;
while(top[u]!=top[k]){
s3=s.find(1,dfn[top[u]],dfn[u]),s4=s1;
pushup(s1,s3,s4);
u=f[top[u]];
}
s3=s.find(1,dfn[k],dfn[u]),s4=s1;
pushup(s1,s3,s4);
while(top[v]!=top[k]){
s3=s.find(1,dfn[top[v]],dfn[v]),s4=s2;
pushup(s2,s3,s4);
v=f[top[v]];
}
s3=s.find(1,dfn[k],dfn[v]),s4=s2;
pushup(s2,s3,s4);
return max(max(s1.lx,s2.rx),s2.mx-s1.mn);
}
void add(int u,int v){
nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void init(){
cin>>n;
for(int i=1;i<=n;i++)cin>>val[i];
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
cin>>m;
dfs1(1,0);dfs2(1,1);
s.build(1,1,n);
}
void solve(){
while(m--){
int u,v,w;
cin>>u>>v>>w;
cout<<query(u,v)<<"\n";
change(u,v,w);
}
}
int main(){
ios::sync_with_stdio(false);
init();solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!