来得挺快的……
还挺简单的……
具体思路……
就是一棵树有目的地分成一条条链,然后按照一个链一个链地操作……
类似于平衡树,一条链就代表一个区间……
em…………
有空补上
# ——4月13日
例一:P3379 【模板】最近公共祖先(LCA)
看个树剖做LCA的例子
P3379 【模板】最近公共祖先(LCA)
还没有打,先把代码放着看看。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 20,M = 2 * N, INF = 0x3f3f3f3f;
int n, m, root;
int h[N], ne[M], idx, e[M];
void add(int x, int y)
{
e[idx] = y, ne[idx] = h[x], h[x] = idx++;
}
void init()
{
memset(h, -1, sizeof h);
}
int p[N], siz[N], son[N], dep[N], f[N];
void dfs1(int x, int fa)
{
dep[x] = dep[fa] + 1;
p[x] = fa;
siz[x] = 1;
int maxsiz = -INF;
for(int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];
if(j == fa) continue;
dfs1(j, x);
siz[x] += siz[j];
if(!son[x] || siz[son[x]] < siz[j])
son[x] = j;
}
}
void dfs2(int x, int fa)
{
f[x] = fa;
if(!son[x]) return;
dfs2(son[x], fa);
for(int i = h[x]; ~i; i = ne[i])
{
int j = e[i];
if(j == p[x] || j == son[x]) continue;
dfs2(j, j);
}
}
int lca(int x, int y)
{
while(f[x] != f[y])
{
if(dep[f[x]] < dep[f[y]]) swap(x, y);
x = p[f[x]];
}
return dep[x] < dep[y] ? x : y;
}
int main() {
init();
scanf("%d%d%d", &n, &m, &root);
for(int i = 1;i < n; i ++)
{
int x, y;scanf("%d%d", &x, &y);
add(x, y), add(y, x);
}
dep[root] = 1;
dfs1(root, -1);
dfs2(root, root);
for(int i = 1; i <= m; i ++)
{
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y));
}
return 0;
}
例二:P3384 【模板】轻重链剖分
再来看一道普通树剖模板题:
P3384 【模板】轻重链剖分
//P3384 【模板】轻重链剖分
#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int n,m,r,mod;
//以下是链式前向星
struct Edge{int to, next;}edge[2*maxn];
int head[2*maxn], cnt;
void init(); //与前一小节“洛谷P3379树链剖分”的init()一样
void addedge(int u,int v); //与前一小节“洛谷P3379树链剖分”的addedge()一样
//以下是线段树
int ls(int x){ return x<<1; } //定位左儿子:x*2
int rs(int x){ return x<<1|1;} //定位右儿子:x*2 + 1
int w[maxn],w_new[maxn]; //w[]、w_new[]初始点权
int tree[maxn<<2], tag[maxn<<2]; //线段树数组、lazy-tag操作
void init(){ //链式前向星:初始化
for(int i=0;i<2*maxn;++i){ edge[i].next = -1; head[i] = -1; }
cnt = 0;
}
void addedge(int u,int v){ //链式前向星:加边
edge[cnt].to = v; edge[cnt].next = head[u]; head[u] = cnt++;
}//以上是链式前向星
void addtag(int p,int pl,int pr,int d){ //给结点p打tag标记,并更新tree
tag[p] += d; //打上tag标记
tree[p] += d*(pr-pl+1); tree[p] %= mod; //计算新的tree
}
void push_up(int p){ //从下往上传递区间值
tree[p] = tree[ls(p)] + tree[rs(p)]; tree[p] %= mod;
}
void push_down(int p,int pl, int pr){
if(tag[p]){
int mid = (pl+pr)>>1;
addtag(ls(p),pl,mid,tag[p]); //把tag标记传给左子树
addtag(rs(p),mid+1,pr,tag[p]); //把tag标记传给右子树
tag[p] = 0;
}
}
void build(int p,int pl,int pr){ //建线段树
tag[p] = 0;
if(pl==pr){
tree[p] = w_new[pl]; tree[p] %= mod;
return;
}
int mid = (pl+pr) >> 1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
}
void update(int L,int R,int p,int pl,int pr,int d){
if(L<=pl && pr<=R){
addtag(p, pl, pr,d);
return;
}
push_down(p,pl,pr);
int mid = (pl+pr) >> 1;
if(L<=mid) update(L,R,ls(p),pl,mid,d);
if(R> mid) update(L,R,rs(p),mid+1,pr,d);
push_up(p);
}
int query(int L,int R,int p,int pl,int pr){
if(pl>=L && R >= pr)
return tree[p] %= mod;
push_down(p,pl,pr);
int res =0;
int mid = (pl+pr) >> 1;
if(L<=mid) res += query(L,R,ls(p),pl,mid);
if(R> mid) res += query(L,R,rs(p),mid+1,pr);
return res;
}
//以下是树链剖分
int son[maxn],id[maxn],fa[maxn],deep[maxn],siz[maxn],top[maxn];
void dfs1(int x, int father){ //与前一小节“洛谷P3379树链剖分”dfs1()一样
deep[x]=deep[father]+1; //深度:比父结点深度多1
fa[x]=father; //标记x的父亲
siz[x]=1; //标记每个结点的子树大小(包括自己)
for(int i=head[x];~i;i=edge[i].next){
int y=edge[i].to;
if(y!=father){ //邻居:除了父亲,都是孩子
fa[y]=x;
dfs1(y,x);
siz[x] += siz[y]; //回溯后,把x的儿子数加到x身上
if(!son[x] || siz[son[x]]<siz[y]) //标记每个非叶子结点的重儿子
son[x]=y; //x的重儿子是y
}
}
}
int num = 0;
void dfs2(int x,int topx){ //x当前节点,topx当前链的最顶端的节点
id[x] = ++num; //对每个结点新编号
w_new[num] = w[x]; //把每个点的初始值赋给新编号
top[x]=topx; //记录x的链头
if(!son[x]) return; //x是叶子,没有儿子,返回
dfs2(son[x],topx); //先dfs重儿子
for(int i=head[x];~i;i=edge[i].next){ //再dfs轻儿子
int y=edge[i].to;
if(y!=fa[x] && y!=son[x])
dfs2(y,y); //每一个轻儿子都有一条从它自己开始的链
}
}
void update_range(int x,int y,int z){ //和求LCA(x, y)的过程差不多
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]])
swap(x,y);
update(id[top[x]],id[x],1,1,n,z); //修改一条重链的内部
x = fa[top[x]];
}
if(deep[x]>deep[y]) swap(x,y);
update(id[x],id[y],1,1,n,z); //修改一条重链的内部
}
int query_range(int x,int y){ //和求LCA(x,y)的过程差不多
int ans=0;
while(top[x]!=top[y]){ //持续往上跳,直到若x和y属于同一条重链
if(deep[top[x]]<deep[top[y]])
swap(x,y); //让x是链头更深的重链
ans += query(id[top[x]],id[x],1,1,n); //加上x到x的链头这一段区间
ans %= mod;
x = fa[top[x]]; //x穿过轻边,跳到上一条重链
}
if(deep[x]>deep[y]) //若LCA(x, y) = y,交换x, y
swap(x,y); //让x更浅,使得id[x] <= id[y]
ans += query(id[x],id[y],1,1,n); //再加上x, y的区间和
return ans % mod;
}
void update_tree(int x,int k){ update(id[x],id[x]+siz[x]-1,1,1,n,k); }
int query_tree(int x){ return query(id[x],id[x]+siz[x]-1,1,1,n) % mod; }
int main(){
init(); //链式前向星初始化
scanf("%d%d%d%d",&n,&m,&r,&mod);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<n;i++){
int u,v; scanf("%d%d",&u,&v);
addedge(u,v); addedge(v,u);
}
dfs1(r,0);
dfs2(r,r);
build(1,1,n); //建线段树
while(m--){
int k,x,y,z; scanf("%d",&k);
switch(k){
case 1: scanf("%d%d%d",&x,&y,&z);update_range(x,y,z); break;
case 2: scanf("%d%d",&x,&y); printf("%d\n",query_range(x,y));break;
case 3: scanf("%d%d",&x,&y); update_tree(x,y); break;
case 4: scanf("%d",&x); printf("%d\n",query_tree(x)); break;
}
}
}