20230626-树链剖分+点分治

20230626

重链剖分

  1. 计算每个节点子树大小,判断出每个点的重儿子
  2. 优先遍历重儿子连边,并且按照 DFS 序重新编号

特点:

  1. 每条重链上的点编号是连续的
  2. 每个点为根的子树内所有点的编号是连续的 $ \to $线段树

需求:

  1. 对于树上两点 \(x,y\) 路径上的所有点进行操作
    必然不能避免的事情就是 LCA,寻找最近公共祖先
    按照每条链上跳,跳的时候顺便处理掉这段区间的值
  2. 对于树上两点 \(x,y\) 路径上的所有点求和,根上述操作一致
  3. 对于树上某个点 \(x\) 为根 的子树整个进行操作
    区间为(id[x], id[x] + siz[x] - 1)

P3384 【模板】重链剖分/树链剖分

题目大意

传送门
一棵 \(n\) 个节点的树,节点带权。支持链加、链求和、子树加、子树求和。
\(n, m \le 10^5\)

Solution

一道树链剖分的板子题
树剖之后把每个节点转移到线段树上进行区间操作即可
注意细节!!!

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
#define ll long long 

const int maxn=1e5+10;
int n,m,r,dep[maxn],fa[maxn],top[maxn],siz[maxn],id[maxn],rev[maxn],son[maxn],head[maxn],tot=0,cnt=0;
ll mod,a[maxn];
struct edge{
  int v,nxt;
}e[maxn*2];
struct seg{
  ll val,tag;
}tr[maxn*4];

namespace SGT{
  #define mid (l+r)/2
  #define lson l,mid,rt<<1
  #define rson mid+1,r,rt<<1|1
  #define lc rt<<1
  #define rc rt<<1|1
  inline void pushup(int rt){tr[rt].val=(tr[lc].val+tr[rc].val)%mod;}
  inline void pushdown(int l,int r,int rt){
  	if(tr[rt].tag>0){
  	  int t=tr[rt].tag;
  	  tr[rt].tag=0;
  	  tr[lc].val=(tr[lc].val+t*(mid-l+1))%mod;
  	  tr[rc].val=(tr[rc].val+t*(r-mid))%mod;
  	  tr[lc].tag=(tr[lc].tag+t)%mod;//+t而不是=t 
  	  tr[rc].tag=(tr[rc].tag+t)%mod;
	}
  }
  inline void build(int l=1,int r=n,int rt=1){
    if(l==r){
      tr[rt].val=rev[l]%mod;
      tr[rt].tag=0;
      return;
	}
	build(lson);
	build(rson);
	pushup(rt);
  }
  inline void update(int x,int y,ll val,int l=1,int r=n,int rt=1){
  	if(x<=l&&y>=r){
	  tr[rt].val=(tr[rt].val+val*(r-l+1))%mod;
	  tr[rt].tag=(tr[rt].tag+val)%mod;
	  return;  
	}
	pushdown(l,r,rt);
	if(x<=mid) update(x,y,val,lson);
	if(y>mid) update(x,y,val,rson);
	pushup(rt);
  }
  inline ll query(int x,int y,int l=1,int r=n,int rt=1){
  	if(x<=l&&y>=r) return tr[rt].val;
  	pushdown(l,r,rt);
  	ll res=0;
  	if(x<=mid) res=(res+query(x,y,lson))%mod;
  	if(y>mid) res=(res+query(x,y,rson))%mod;
  	return res%mod;
  }
}

inline void add(int u,int v){
  e[++tot]=(edge){v,head[u]};
  head[u]=tot;
  e[++tot]=(edge){u,head[v]};
  head[v]=tot;
}

inline void dfs1(int u,int f){
  dep[u]=dep[f]+1;
  fa[u]=f;siz[u]=1;
  son[u]=-1;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==f) continue;
  	dfs1(v,u);
  	siz[u]+=siz[v];
  	if(son[u]==-1||siz[v]>siz[son[u]]) son[u]=v;
  }
}

inline void dfs2(int u,int pre){
  top[u]=pre;
  id[u]=++cnt;rev[cnt]=a[u];
  if(son[u]==-1) return ;
  dfs2(son[u],pre);
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa[u]||v==son[u]) continue;
  	dfs2(v,v);
  }
}

inline void add_path(int x,int y,ll val){
  val%=mod;
  while(top[x]!=top[y]){
  	if(dep[top[x]]<dep[top[y]]) swap(x,y);
  	SGT::update(id[top[x]],id[x],val);
  	x=fa[top[x]];
  }
  if(dep[x]>dep[y]) swap(x,y);
  SGT::update(id[x],id[y],val);
}

inline ll query_path(int x,int y){
  ll res=0;
  while(top[x]!=top[y]){
  	if(dep[top[x]]<dep[top[y]]) swap(x,y);
  	res=(res+SGT::query(id[top[x]],id[x]))%mod;
  	x=fa[top[x]];
  }
  if(dep[x]>dep[y]) swap(x,y);
  res=(res+SGT::query(id[x],id[y]))%mod;
  return res;
}

int main(){
  /*2023.7.3 H_W_Y P3384 【模板】重链剖分/树链剖分 树链剖分*/ 
  scanf("%d%d%d%lld",&n,&m,&r,&mod);
  for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
  for(int i=1;i<n;i++){
  	int u,v;
  	scanf("%d%d",&u,&v);
  	add(u,v);
  }
  dfs1(r,0);dfs2(r,r);//dfs2中的pre是top[u],所以是dfs2(r,r) 
  SGT::build();
  for(int i=1;i<=m;i++){
  	int op,u,v;ll val;
  	scanf("%d",&op);
  	if(op==1){
  	  scanf("%d%d%lld",&u,&v,&val);
	  add_path(u,v,val);
	}
	if(op==2){
	  scanf("%d%d",&u,&v);
	  printf("%lld\n",query_path(u,v)%mod);	
	}
	if(op==3){
	  scanf("%d%lld",&u,&val);
	  SGT::update(id[u],id[u]+siz[u]-1,val);
	}
	if(op==4){
	  scanf("%d",&u);
	  printf("%lld\n",SGT::query(id[u],id[u]+siz[u]-1)%mod);
	}
  }
  return 0;
}

P4211 [LNOI2014] LCA

题目大意

传送门

一棵 \(n\) 个节点的有根树,定义 \(dep_x\)\(x\) 到根的距离 \(+1\)\(q\) 次给出 \(l, r, z\),询问
\(\sum_{i=l}^{r}{dep[LCA(i,z)]}\)
\(n,q \le 50000\)

Solution

\(\sum dep\),暴力求sum是完不成的
那我们考虑深度是什么?
\(x\)到根的距离\(+1\)
而对于每一条边
它的距离都为\(1\)
这样我们就可以把距离转化到点上面
我们把\(l \sim r\)到根的路径上的点的权值都赋值为\(1\)
那么在找从\(z\)到根的路径上的权值之和即可

而对于这个问题
我的想法是用一棵主席树来维护
\(\to\) a week later \(\to\)
不过把树链剖分做到主席树上是真的难!
你还要判断是不是要新建节点的问题
而且空间也会爆
不知道为什么 Wa+RE

所以,为什么要一定用在线?
这道题是可以离线的啊
考虑到用主席树维护其实就是差分
那我们可以把询问离线下来
在从 \(1 \sim n\) 一次加点
再在过程中进行查询即可
这样就不用用主席树了,好写多了
一发就过了

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int N=1e5+5,mod=201314;
int n,m,f[N],dep[N],siz[N],son[N],top[N],id[N],cnt=0,ans[N],head[N],tot=0;
struct edge{
  int v,nxt;
}e[N<<1];
struct node{
  int l,r,z;
}a[N];
vector<vector<int> > g(N);

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

void add(int u,int v){
  e[++tot]=(edge){v,head[u]};
  head[u]=tot;
}

void dfs1(int u){
  dep[u]=dep[f[u]]+1;siz[u]=1;son[u]=-1;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==f[u]) continue;
  	dfs1(v);
  	siz[u]+=siz[v];
  	if(son[u]==-1||siz[v]>siz[son[u]]) son[u]=v;
  }
}

void dfs2(int u,int pre){
  top[u]=pre;id[u]=++cnt;
  if(son[u]==-1) return ;
  dfs2(son[u],pre);
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==f[u]||v==son[u]) continue;
  	dfs2(v,v);
  }
}

struct seg{
  int val,tag;
}tr[N<<5];

#define mid ((l+r)>>1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define lc rt<<1
#define rc rt<<1|1
void pushdown(int l,int r,int rt){
  if(tr[rt].tag){
  	tr[lc].tag=(tr[lc].tag+tr[rt].tag)%mod;
  	tr[rc].tag=(tr[rc].tag+tr[rt].tag)%mod;
  	tr[lc].val=(tr[lc].val+(mid-l+1)*tr[rt].tag%mod)%mod;
  	tr[rc].val=(tr[rc].val+(r-mid)*tr[rt].tag%mod)%mod;
  	tr[rt].tag=0;
  }
}

void pushup(int rt){tr[rt].val=(tr[lc].val+tr[rc].val)%mod;}

void update(int l,int r,int rt,int a,int b){
  if(a<=l&&b>=r){
  	tr[rt].tag=(tr[rt].tag+1)%mod;
  	tr[rt].val=(tr[rt].val+r-l+1)%mod;
  	return;
  }
  pushdown(l,r,rt);
  if(a<=mid) update(lson,a,b);
  if(b>mid) update(rson,a,b);
  pushup(rt);
}

int query(int l,int r,int rt,int a,int b){
  if(a<=l&&b>=r) return tr[rt].val;
  pushdown(l,r,rt);
  int res=0;
  if(a<=mid) res=(res+query(lson,a,b))%mod;
  if(b>mid) res=(res+query(rson,a,b))%mod;
  return res%mod;
}

void upd(int u,int v){
  while(top[u]!=top[v]){
  	if(dep[top[u]]<dep[top[v]]) swap(u,v);
  	update(1,n,1,id[top[u]],id[u]);
  	u=f[top[u]];
  }
  if(dep[u]>dep[v]) swap(u,v);
  update(1,n,1,id[u],id[v]);
}

int qry(int u,int v){
  int res=0;
  while(top[u]!=top[v]){
  	if(dep[top[u]]<dep[top[v]]) swap(u,v);
  	res=(res+query(1,n,1,id[top[u]],id[u]))%mod;
  	u=f[top[u]];
  }
  if(dep[u]>dep[v]) swap(u,v);
  res=(res+query(1,n,1,id[u],id[v]))%mod;
  return res;
}

int main(){
  /*2023.8.1 H_W_Y P4211 [LNOI2014] LCA 树链剖分+线段树*/ 
  n=read();m=read();
  for(int i=2;i<=n;i++){
  	f[i]=read();f[i]++;
  	add(f[i],i);
  }
  dfs1(1);dfs2(1,1);
  for(int i=1;i<=m;i++){
  	a[i].l=read();a[i].r=read();a[i].z=read();
  	a[i].l++;a[i].r++;a[i].z++;
  	g[a[i].l-1].push_back(i);
  	g[a[i].r].push_back(i);
  }
  for(int i=1;i<=n;i++){
  	upd(1,i);
  	for(int j=0;j<g[i].size();j++){
  	  if(a[g[i][j]].r==i) ans[g[i][j]]=(ans[g[i][j]]+qry(1,a[g[i][j]].z)+mod)%mod;
	  else ans[g[i][j]]-=qry(1,a[g[i][j]].z);	
    }
  }
  for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
  return 0;
}

P2486 [SDOI2011] 染色

题目描述

传送门
给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:

  1. 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\)\(b\))都染成颜色 \(c\)
  2. 询问节点 \(a\) 到节点 \(b\) 的路径上的颜色段数量例如 ′112221′ 由三段组成:′11′、′222′、′1′。
    \(n, m \le 10^5\)

Solution

很板的一道题了
就用线段树维护一下就好了
可是我活生生地调了两个小时……

  1. 注意括号匹配!
  2. 树边的输入是 \(1 \sim n-1\)
  3. 注意合并时是用\(l\)还是\(r\)
H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,m,c[N],f[N],top[N],id[N],cnt=0,dep[N],siz[N],son[N],head[N],tot=0,rev[N];
char ch[5];
struct edge{
  int v,nxt;
}e[N<<1];
struct seg{
  int sum,tag,lc,rc;
  seg(int s=0,int t=0,int l=0,int r=0){sum=s,tag=t;lc=l,rc=r;}
}tr[N<<3];

void add(int u,int v){
  e[++tot]=(edge){v,head[u]};
  head[u]=tot;
  e[++tot]=(edge){u,head[v]};
  head[v]=tot;
}

void dfs1(int u,int fa){
  dep[u]=dep[fa]+1;f[u]=fa;
  siz[u]=1;son[u]=-1;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa) continue;
  	dfs1(v,u);
  	siz[u]+=siz[v];
  	if(son[u]==-1||siz[v]>siz[son[u]]) son[u]=v;
  }
}

void dfs2(int u,int pre){
  top[u]=pre;
  id[u]=++cnt;rev[cnt]=u;
  if(son[u]==-1) return ;
  dfs2(son[u],pre);
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==f[u]||v==son[u]) continue;
  	dfs2(v,v);
  }
}

namespace SGT{
  #define mid (l+r)/2
  #define lson l,mid,rt<<1
  #define rson mid+1,r,rt<<1|1
  void pushup(int rt){
    tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum-((tr[rt<<1].rc==tr[rt<<1|1].lc)?1:0);
    tr[rt].lc=tr[rt<<1].lc;tr[rt].rc=tr[rt<<1|1].rc;
  }
  void pushdown(int rt){
  	if(tr[rt].tag){
  	  tr[rt<<1]=tr[rt<<1|1]=(seg){1,tr[rt].tag,tr[rt].tag,tr[rt].tag}; 
	  tr[rt].tag=0;	
	}
  }
  void build(int l,int r,int rt){
    if(l==r){
      tr[rt]=(seg){1,0,c[rev[l]],c[rev[l]]};
      return;
	}
	build(lson);build(rson);
	pushup(rt);
  }
  void update(int l,int r,int rt,int a,int b,int x){
    if(a<=l&&b>=r){
      tr[rt]=(seg){1,x,x,x};
      return;
	}
	pushdown(rt);
	if(a<=mid) update(lson,a,b,x);
	if(b>mid) update(rson,a,b,x);
	pushup(rt);
  }
  seg query(int l,int r,int rt,int a,int b){
  	if(a<=l&&b>=r) return tr[rt];
  	pushdown(rt);
  	if(b<=mid) return query(lson,a,b);
  	if(a>mid) return query(rson,a,b);
  	seg ll=query(lson,a,b),rr=query(rson,a,b);
  	return seg(ll.sum+rr.sum-((ll.rc==rr.lc)?1:0),0,ll.lc,rr.rc);
  }
}

void update(int u,int v,int x){
  while(top[u]!=top[v]){
  	if(dep[top[u]]<dep[top[v]]) swap(u,v);
  	SGT::update(1,n,1,id[top[u]],id[u],x);
  	u=f[top[u]];
  }
  if(id[u]>id[v]) swap(u,v);
  SGT::update(1,n,1,id[u],id[v],x);
}

int query(int u,int v){
  seg p;int res=0,ans1=-1,ans2=-1;
  while(top[u]!=top[v]){
  	if(dep[top[u]]<dep[top[v]]) swap(u,v),swap(ans1,ans2);
	p=SGT::query(1,n,1,id[top[u]],id[u]);
	u=f[top[u]];res+=p.sum-((ans1==p.rc)?1:0);
	ans1=p.lc;
  }
  if(id[u]<id[v]) swap(u,v),swap(ans1,ans2);
  p=SGT::query(1,n,1,id[v],id[u]);
  res+=p.sum-((p.lc==ans2)?1:0)-((p.rc==ans1)?1:0);
  return res;
}

int main(){
  /*2023.7.27 H_W_Y P2486 [SDOI2011] 染色 树链剖分+线段树*/ 
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++) scanf("%d",&c[i]);
  for(int i=1,u,v;i<n;i++){
  	scanf("%d%d",&u,&v);
  	add(u,v);
  }
  dfs1(1,0);dfs2(1,0);
  SGT::build(1,n,1);
  for(int i=1,l,r,x;i<=m;i++){
  	scanf("%s",ch);
  	if(ch[0]=='C'){
  	  scanf("%d%d%d",&l,&r,&x);
  	  update(l,r,x);
	}
	if(ch[0]=='Q'){
	  scanf("%d%d",&l,&r);
	  printf("%d\n",query(l,r));
	}
  }
  return 0;
}

P3313 [SDOI2014] 旅行

题目描述

传送门
给定一棵 \(n\) 个节点的无根树,有点权,颜色会更改颜色,点权,查询一条路径上
某颜色点权和或最大值。
\(n, m \le 3 × 10^5\)

Solution

对于每一种颜色,
我们都去建立一棵动态开点的线段树去维护即可
简单易懂

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define lc tr[p].ch[0]
#define rc tr[p].ch[1]

const int N=5e5+5;
int n,m,c[N],a[N],dep[N],id[N],son[N],siz[N],f[N],top[N],rt[N],cnt=0,tot=0,head[N];
char s[15];
struct seg{
  int sum,mx,se;
  int ch[2];
}tr[N<<5];
struct edge{
  int v,nxt;
}e[N<<1];
struct Answer{
  int sum,mx;
  Answer(int x=0,int y=0){sum=x;mx=y;}
  Answer operator +(const Answer &a)const{return Answer(sum+a.sum,((mx>a.mx)?mx:a.mx));}
}ans;

void add(int u,int v){
  e[++tot]=(edge){v,head[u]};
  head[u]=tot;
  e[++tot]=(edge){u,head[v]};
  head[v]=tot;
}

void dfs1(int u,int fa){
  f[u]=fa;dep[u]=dep[fa]+1;siz[u]=1;son[u]=-1;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa) continue;
  	dfs1(v,u);
  	siz[u]+=siz[v];
  	if(son[u]==-1||siz[son[u]]<siz[v]) son[u]=v;
  }
}

void dfs2(int u,int pre){
  top[u]=pre;id[u]=++cnt;
  if(son[u]==-1) return;
  dfs2(son[u],pre);
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==f[u]||v==son[u]) continue;
  	dfs2(v,v);
  }
}

void pushup(int p){
  tr[p].sum=tr[lc].sum+tr[rc].sum;
  if(tr[lc].mx>tr[rc].mx){
  	tr[p].mx=tr[lc].mx;
  	tr[p].se=max(tr[lc].se,tr[rc].mx);
  }
  else {
  	tr[p].mx=tr[rc].mx;
  	tr[p].se=max(tr[rc].se,tr[lc].mx);
  }
}

void update(int &p,int l,int r,int x,int val){
  if(!p) p=++cnt;
  if(l==r){
  	tr[p].sum=val;
  	tr[p].mx=val;
  	return;
  }
  if(x<=mid) update(lc,l,mid,x,val);
  else update(rc,mid+1,r,x,val);
  pushup(p);
}

Answer query(int p,int l,int r,int a,int b){
  if(a<=l&&b>=r) return Answer(tr[p].sum,tr[p].mx);
  Answer res=(Answer){0,0}; 
  if(a<=mid&&lc) res=res+query(lc,l,mid,a,b);
  if(b>mid&&rc) res=res+query(rc,mid+1,r,a,b);
  return res;
}

Answer qry(int u,int v){
  Answer res=(Answer){0,0};
  int col=c[u];
  while(top[u]!=top[v]){
  	if(dep[top[u]]<dep[top[v]]) swap(u,v);
  	res=res+query(rt[col],1,n,id[top[u]],id[u]);
  	u=f[top[u]];
  }
  if(dep[u]>dep[v]) swap(u,v);
  res=res+query(rt[col],1,n,id[u],id[v]);
  return res;
}

int main(){
  /*2023.7.28 H_W_Y P3313 [SDOI2014] 旅行 树链剖分+线段树*/ 
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&c[i]);
  for(int i=1,u,v;i<n;i++){
  	scanf("%d%d",&u,&v);
  	add(u,v);
  }
  dfs1(1,0);dfs2(1,1);
  for(int i=1;i<=n;i++) update(rt[c[i]],1,n,id[i],a[i]);
  for(int i=1,x,y;i<=m;i++){
  	scanf("%s",s);
    scanf("%d%d",&x,&y);	
  	if(s[0]=='C'&&s[1]=='C'){
	  update(rt[c[x]],1,n,id[x],0);c[x]=y;
	  update(rt[c[x]],1,n,id[x],a[x]);	
	}
	if(s[0]=='C'&&s[1]=='W') update(rt[c[x]],1,n,id[x],y),a[x]=y;
	if(s[0]=='Q'){
	  ans=qry(x,y);
	  if(s[1]=='S') printf("%d\n",ans.sum);
	  else printf("%d\n",ans.mx);
	}
  }
  return 0;
}

点分治

P3806 【模板】点分治1

题目大意

传送门
给定一棵 \(n\) 个点的树,询问是否存在距离为 \(k\) 的点对。
\(n \le 10^5\)

Solution

一道点分治的模板题
通常被用在大规模处理树上路径的问题上(如同时询问树上所有路径的答案和)。

考虑把树上的路径分为两种
一种是经过根节点的,另一种是不经过根节点的
我们对于每一个节点,考虑第一种路径
又分为端点为根节点的和两个端点都不是根节点的
那么后者是由前者组成的
我们只要统计之前是否出现过\(q[i]-dist[i]\)的路径
就可以判断是否出现长度为\(q[i]\)的路径

而对于第二种路径
我们递归其子树即可

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10,inf=0x3f3f3f3f;
int n,m,head[maxn],rt,cnt=0,sum,tot=0,d[maxn],dist[maxn],q[maxn],mx[maxn],siz[maxn];
bool vis[maxn],tf[10000005],res[maxn];
struct edge{
  int v,nxt,val;
}e[maxn*2];

inline void add(int u,int v,int val){
  e[++tot]=(edge){v,head[u],val};
  head[u]=tot;
  e[++tot]=(edge){u,head[v],val};
  head[v]=tot;
}

inline int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

inline void calcsiz(int u,int fa){
  siz[u]=1;mx[u]=0;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa||vis[v]) continue;
  	calcsiz(v,u);
  	siz[u]+=siz[v];
  	mx[u]=max(mx[u],siz[v]);
  }
  mx[u]=max(mx[u],sum-siz[u]);
  if(mx[u]<mx[rt]) rt=u;
}

inline void calcdist(int u,int fa){
  d[++cnt]=dist[u];
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v,val=e[i].val;
  	if(v==fa||vis[v]) continue;
  	dist[v]=dist[u]+val;
  	calcdist(v,u);
  }
}

queue<int>tag;
inline void dfz(int u,int fa){
  tf[0]=true;//初始化 
  tag.push(0);
  vis[u]=true;
  cnt=0;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v,val=e[i].val;
  	if(v==fa||vis[v]) continue;
  	dist[v]=val;
  	calcdist(v,u);
  	for(int j=1;j<=cnt;j++)
  	  for(int k=1;k<=m;k++)
  	    if(q[k]>=d[j]) res[k]|=tf[q[k]-d[j]];//不要忽略以根为端点的情况 
  	for(int j=1;j<=cnt;j++) if(d[j]<10000000) tf[d[j]]=true,tag.push(d[j]);//注意d[j]的范围,否则会RE 
    cnt=0;
  }
  while(!tag.empty()) tf[tag.front()]=false,tag.pop();
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa||vis[v]) continue;
  	sum=siz[v];rt=0;mx[0]=inf;
  	calcsiz(v,u);calcsiz(rt,0);
  	dfz(rt,u);
  }
}

int main(){
  /*2023.7.4 H_W_Y P3806 【模板】点分治1 点分治*/
  n=read();m=read();
  for(int i=1;i<n;i++){
  	int u,v,w;
  	u=read();v=read();w=read();
  	add(u,v,w);
  }
  for(int i=1;i<=m;i++) q[i]=read();
  sum=n;rt=0;mx[rt]=inf;
  calcsiz(1,0);calcsiz(rt,0);
  dfz(rt,0);
  for(int i=1;i<=m;i++)
    if(res[i]) printf("AYE\n");
    else printf("NAY\n");
  return 0;
}

P2634 [国家集训队] 聪聪可可

题目大意

传送门
给定一棵 \(n\) 个点的树,求树上距离为 \(3\) 的倍数的点的对数。
\(n \le 10^5\)

Solution

一道点分治的模板题
对于每条路径统计即可
注意是两个不同的人选
所以计算方案数时要乘上2\(A_{2}^{2}=2\)
其他的部分和上一道题没有太大区别

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10,inf=0x3f3f3f3f;
int tf[4],n,head[maxn],tot=0,cnt=0,ans=0,siz[maxn],d[3],dis[maxn],mx[maxn],rt=0,sum;
bool vis[maxn];
struct edge{
  int v,nxt,val;
}e[maxn*2];

inline int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

inline void add(int u,int v,int val){
  e[++tot]=(edge){v,head[u],val%3};
  head[u]=tot;
  e[++tot]=(edge){u,head[v],val%3};
  head[v]=tot;
}

inline void calcsiz(int u,int fa){
  siz[u]=1;mx[u]=0;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa||vis[v]) continue;
  	calcsiz(v,u);
  	siz[u]+=siz[v];
  	mx[u]=max(mx[u],siz[v]);
  }
  mx[u]=max(mx[u],sum-siz[u]);
  if(mx[u]<mx[rt]) rt=u;
}

inline void calcdist(int u,int fa){
  d[dis[u]%3]++;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v,val=e[i].val;
  	if(v==fa||vis[v]) continue;
  	dis[v]=dis[u]+val;
  	calcdist(v,u);
  }
}

inline void dfz(int u,int fa){
  tf[0]=1;
  vis[u]=true;ans++;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v,val=e[i].val;
  	if(v==fa||vis[v]) continue;
  	dis[v]=val;
  	calcdist(v,u);
  	for(int j=0;j<=2;j++)
  	  ans+=tf[(3-j)%3]*d[j]*2;//两个人去选,对于一个点对有A_2^2=2种情况,所以要*2 
  	for(int j=0;j<=2;j++) tf[j]+=d[j];
  	d[0]=d[1]=d[2]=0;
  }
  tf[0]=tf[1]=tf[2]=0;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa||vis[v]) continue;
  	rt=0;mx[rt]=inf;sum=siz[v];
  	calcsiz(v,u);calcsiz(rt,0);
  	dfz(rt,u);
  }
}

inline int gcd(int x,int y){
  if(y==0) return x;
  return gcd(y,x%y);
}

int main(){
  /*2023.7.4 H_W_Y P2634 [国家集训队] 聪聪可可 点分治*/ 
  n=read();
  for(int i=1;i<n;i++){
    int u,v,val;
	u=read();v=read();val=read();
	add(u,v,val); 
  }
  rt=0;mx[rt]=inf;sum=n;
  calcsiz(1,0);calcsiz(rt,0);
  dfz(rt,0);
  sum=n*n;
  int t=gcd(ans,sum);
  printf("%d/%d\n",ans/t,sum/t);
  return 0;
}

P4149 [IOI2011] Race

题目大意

传送门
给定一棵 \(n\) 个点的树,有边权,求树上距离为 \(k\) 的点对之间的边数最小值。
\(n \le 10^5\)

Solution

和点分治的模板题比较类似
就是在统计是否出现长度为\(k\)的路径同时统计其边的条数
在每一次更新的时候对答案也进行更新即可
注意细节问题:
\(v=e[i].v \ne e[i].nxt\)

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=2e5+10,inf=0x3f3f3f3f;
int n,k,head[maxn],tot=0,cnt=0,d[maxn],dis[maxn],siz[maxn],rt,sum,mn[1000005],ans,dd[maxn],mx[maxn];
bool res=false,tf[1000005],vis[maxn];
struct edge{
  int v,nxt,val;
}e[maxn*2];

inline int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

inline void add(int u,int v,int val){
  e[++tot]=(edge){v,head[u],val};
  head[u]=tot;
  e[++tot]=(edge){u,head[v],val};
  head[v]=tot;
}

inline void calcsiz(int u,int fa){
  mx[u]=0;siz[u]=1;
  for(int i=head[u];i;i=e[i].nxt){
    int v=e[i].v;
    if(v==fa||vis[v]) continue;
    calcsiz(v,u);
    siz[u]+=siz[v];
    mx[u]=max(mx[u],siz[v]);
  }
  mx[u]=max(mx[u],sum-siz[u]);
  if(mx[u]<mx[rt]) rt=u;
}

inline void calcdist(int u,int fa,int step){
  d[++cnt]=dis[u];dd[cnt]=step;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v,val=e[i].val;
  	if(v==fa||vis[v]) continue;
  	dis[v]=dis[u]+val;
  	calcdist(v,u,step+1);
  }
}

queue<int>tag;
inline void dfz(int u,int fa){
  vis[u]=true;
  tf[0]=true;mn[0]=0;
  tag.push(0);
  cnt=0;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v,val=e[i].val;
  	if(v==fa||vis[v]) continue;
  	dis[v]=val;
  	calcdist(v,u,1);
  	for(int j=1;j<=cnt;j++)
  	  if(k>=d[j])
  	    if(tf[k-d[j]]){
  	      res=true;
		  ans=min(ans,mn[k-d[j]]+dd[j]);	
	    }
	for(int j=1;j<=cnt;j++)
	  if(d[j]<=1000000){
	  	tf[d[j]]=true;
	  	tag.push(d[j]);
	  	mn[d[j]]=min(mn[d[j]],dd[j]); 
	  }
	cnt=0;
  }
  while(!tag.empty()) tf[tag.front()]=false,mn[tag.front()]=inf,tag.pop();
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa||vis[v]) continue;
  	rt=0;mx[rt]=inf;sum=siz[v];
  	calcsiz(v,u);calcsiz(rt,0);
  	dfz(rt,u);
  }
}

int main(){
  /*2023.7.4 H_W_Y P4149 [IOI2011] Race 点分治*/ 
  n=read();k=read();
  for(int i=1;i<n;i++){
  	int u,v,val;
  	u=read();v=read();val=read();
  	u++,v++;
	add(u,v,val);
  }
  rt=0;mx[rt]=inf;sum=n;
  ans=inf;
  memset(mn,0x3f,sizeof(mn));
  calcsiz(1,0);calcsiz(rt,0);
  dfz(rt,0);
  if(res) printf("%d\n",ans);
  else printf("-1\n");
  return 0;
}
posted @ 2023-07-19 22:43  H_W_Y  阅读(18)  评论(0编辑  收藏  举报