点分治&动态点分治小结

时间:2023-03-09 15:21:14
点分治&动态点分治小结

(写篇博客证明自己还活着×2)

转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/8006488.html

有的时候,我们会发现这样一类题:它长得很像一个$O(n)$的树规,

但是却很难用单独的数组维护对应的信息,这样我们就有了淀粉质点分治。

通过直接统计($O(nlogn)$)或者加上数据结构(比如树状数组,堆,线段树等等)维护信息($O(nlog^{2}n)$),

我们可以统计之前不好统计的东西

这篇博客将会介绍点分治以及动态点分治的简单应用,希望阅读本文的你能有所收获,那就再好不过了。

一.树规到点分治

我们先来看一道简单的题目……

2152: 聪聪可可

Time Limit: 3 Sec  Memory Limit: 259 MB

Description

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

Input

输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。

Output

以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。

Sample Input

5
1 2 1
1 3 2
1 4 1
2 5 3

Sample Output

13/25
【样例说明】
13组点对分别是(1,1) (2,2) (2,3) (2,5) (3,2) (3,3) (3,4) (3,5) (4,3) (4,4) (5,2) (5,3) (5,5)。

【数据规模】
对于100%的数据,n<=20000。

拿到本题,一个显然的思路是统计长度为3的倍数的路径个数,再比上总的路径个数即是答案。
那么我们发现,3的倍数%3=0(废话……),因此我们可以设$f[i][0/1/2]$为以i为根的子树,向下伸出的路径中长度%3=0/1/2的路径数。
这样我们在每一个点简单的合并一下(注意去掉同一子树的贡献)即可。
这样的做法显然是$O(n)$的对吧……
现在让我们考虑一个蠢一点的做法:
我们依然考虑每个点作为路径顶点时符合条件的点的个数,那么我们可以考虑它的每个子树,
数一下里面每个路径的条数,然后合起来对吧。
对于这个节点子树中的节点,我们是化作子问题递归处理的。
那么我们可以把树换个形状……换一个顺序来递归处理
由于重心有个性质:到其他点的距离和最小,
那我们可以暴力数一下每种长度0/1/2的路径有多少,再暴力加上,然后对于被这个重心分割出的几个子树分治递归解决……
这样找重心的复杂度呢?我们最多找$log$次重心就会只剩下一个点,因此复杂度为$O(nlogn)$.(因为每次找到一个重心,新的子树最多是原来子树大小的一半)
那么介绍一下我们必须的操作:
首先是初始化以及数组定义。我的习惯是这样的:
 int size[N],maxs[N],totsize,root;
bool vis[N];
inline void intn()
{
maxs[]=inf,root=,dfs1(,),solve(root);
}

其中$size[i]$表示目前以i点为根的子树size大小 $maxs[i]$为i点size最大的儿子的size(这个“儿子”可以是i点在有根树中的父亲)

$totsize$为当前联通块大小 $root$为最终的重心 $vis[i]$表示i点是否已经被选取作为重心。vis保证我们可以“截取”出准确的对应联通块然后是找重心的部分:
 inline void dfs1(int rt,int fa)
{
size[rt]=,maxsize[rt]=;
for(int i=adj[rt];i;i=s[i].next)
if(s[i].zhong!=fa&&!mark[s[i].zhong])
{
dfs1(s[i].zhong,rt),
size[rt]+=size[s[i].zhong],
maxsize[rt]=max(maxsize[rt],size[s[i].zhong]);
}
maxsize[rt]=max(maxsize[rt],totsize-maxsize[rt]);
if(maxsize[rt]<maxsize[root])root=rt;
}

这其实类似一个树规的$O(n)$过程……这样找到最后$root$就是重心。然后是solve过程:

 inline void solve(int rt)
{
vis[rt]=;
  /*
    deal with ans
  */
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong])
totsize=size[s[i].zhong],root=,
dfs1(s[i].zhong,),solve(root);
}

我们在每一次确定的重心上进行计算,calc函数的内容随题目而变,但是大概是长这样的……本题的完整代码:

 #include <cstdio>
#include <cstring>
#define N 20010
#define inf 0x7fffffff
#define max(a,b) ((a)>(b)?(a):(b))
int n,e,adj[N],root,totsize,size[N],maxsize[N];
struct edge{int zhong,val,next;}s[N<<];
inline void add(int qi,int zhong,int val)
{s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;s[e].val=val;}
bool mark[N];
inline void dfs1(int rt,int fa)
{
size[rt]=,maxsize[rt]=;
for(int i=adj[rt];i;i=s[i].next)
if(!mark[s[i].zhong]&&s[i].zhong!=fa)
dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong],
maxsize[rt]=max(maxsize[rt],size[s[i].zhong]);
maxsize[rt]=max(maxsize[rt],totsize-size[rt]);
if(maxsize[rt]<maxsize[root])root=rt;
}
int cnt[],dist[N],ans;
inline void dfs2(int rt,int fa)
{
++cnt[dist[rt]];
for(int i=adj[rt];i;i=s[i].next)
if(!mark[s[i].zhong]&&s[i].zhong!=fa)
dist[s[i].zhong]=(dist[rt]+s[i].val)%,
dfs2(s[i].zhong,rt);
}
inline int calc(int rt,int stval)
{
dist[rt]=stval,cnt[]=cnt[]=cnt[]=,dfs2(rt,);
return cnt[]*cnt[]+*cnt[]*cnt[];
}
inline void solve(int rt)
{
mark[rt]=,ans+=calc(rt,);
for(int i=adj[rt];i;i=s[i].next)
if(!mark[s[i].zhong])
ans-=calc(s[i].zhong,s[i].val),
totsize=size[s[i].zhong],root=,
dfs1(s[i].zhong,),solve(root);
}
inline int gcd(int a,int b){return b==?a:gcd(b,a%b);}
int main()
{
scanf("%d",&n);
register int i,a,b,c;
for(i=;i<n;++i)
scanf("%d%d%d",&a,&b,&c),add(a,b,c%),add(b,a,c%);
totsize=n,root=,maxsize[]=inf;
dfs1(,),solve(root);
int di=n*n,d=gcd(di,ans);
printf("%d/%d\n",ans/d,di/d);
}

BZOJ2152

现在这个思路看起来比较蠢对吧……但是如果换一道题,原本的树规做法就行不通了。

2599: [IOI2011]Race

Time Limit: 70 Sec  Memory Limit: 128 MB

Description给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000
Input第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始)
Output一个整数 表示最小边数量 如果不存在这样的路径 输出-1
Sample Input
4 3
0 1 1
1 2 2
1 3 4
Sample Output
2
看到这道题,原本树规的做法显然行不通了:
如果我们定义$f[i][j]$为以i为根的子树中长度为j的路径最小边数,空间和时间复杂度都会爆炸。
这个时候我们再想想刚才点分治的做法:
我们仅用一个数组$f[j]$表示到每个重心之后子树中长度为j的路径最小边数,
然后到每个重心之后通过dfs遍历去更新对应的f值,然后再用f数组之间相加==k的元素之和更新答案。
这样,我们就用一个$log$的代价,解决了树规解决很困难的问题。时间复杂度$O(nlogn)$.
代码: 
 #include <cstdio>
#include <cstring>
using namespace std;
#define N 200010
#define K 1000010
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
int n,k,e,adj[N],ans,cnt[K];
bool vis[N];
int root,tot,size[N],maxs[N];
LL dis[N];
struct edge{int zhong,val,next;}s[N<<];
inline void add(int qi,int zhong,int val)
{s[++e].zhong=zhong;s[e].val=val;s[e].next=adj[qi];adj[qi]=e;}
inline void dfs1(int rt,int fa)
{
size[rt]=,maxs[rt]=;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong]&&s[i].zhong!=fa)
dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong],maxs[rt]=max(maxs[rt],size[s[i].zhong]);
maxs[rt]=max(maxs[rt],tot-size[rt]);
if(maxs[rt]<maxs[root])root=rt;
}
inline void dfs2(int rt,int fa,int deep)
{
if(dis[rt]>=&&dis[rt]<=k)ans=min(ans,deep+cnt[k-dis[rt]]);
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong]&&s[i].zhong!=fa)
dis[s[i].zhong]=dis[rt]+s[i].val,dfs2(s[i].zhong,rt,deep+);
}
inline void update(int rt,int fa,int deep,bool opt)
{
if(dis[rt]>=&&dis[rt]<=k)
if(opt)cnt[dis[rt]]=min(cnt[dis[rt]],deep);
else cnt[dis[rt]]=n;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong]&&s[i].zhong!=fa)
update(s[i].zhong,rt,deep+,opt);
}
inline void solve(int rt)
{
vis[rt]=;cnt[]=;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong])
dis[s[i].zhong]=s[i].val,dfs2(s[i].zhong,,),update(s[i].zhong,,,);
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong])
update(s[i].zhong,,,);
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong])
root=,tot=size[s[i].zhong],dfs1(s[i].zhong,),solve(root);
}
int main()
{
// freopen("Ark.in","r",stdin);
register int i,a,b,c;scanf("%d%d",&n,&k),ans=n;
for(i=;i<n;++i)scanf("%d%d%d",&a,&b,&c),++a,++b,add(a,b,c),add(b,a,c);
for(i=;i<=k;++i)cnt[i]=n;
root=,maxs[]=n,tot=n,dfs1(,),solve(root),
printf("%d\n",(ans==n)?-:ans);
}

BZOJ2599

点分治最核心的思想就是这样,找到重心,分别计算。其他的习题还有:

bzoj4016 (建最短路树,再点分)

 #include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define N 30010
#define inf 0x3fffffff
#define max(a,b) ((a)>(b)?(a):(b))
int n,m,k,e,adj[N],q[N<<],hd,tl;bool vis[N];
struct edge{int to,next,val;}s[N<<];
inline void add(int qi,int to,int val)
{s[++e].to=to;s[e].next=adj[qi];adj[qi]=e;s[e].val=val;}
int totsize,root,size[N],maxs[N],maxl[N],cnt[N],dis[N],ans=-inf,tot;
struct node{int to,val;};vector<node>to[N];
inline bool mt(const node &a,const node &b){return a.to<b.to;}
inline void dfs0(int rt)
{
vis[rt]=;
for(int i=,l=to[rt].size();i<l;++i)
if(!vis[to[rt][i].to]&&dis[to[rt][i].to]==dis[rt]+to[rt][i].val)
add(rt,to[rt][i].to,to[rt][i].val),add(to[rt][i].to,rt,to[rt][i].val),dfs0(to[rt][i].to);
}
inline void spfa_and_build()
{
memset(dis,0x3f,sizeof(dis));
q[hd=tl=]=,vis[]=,dis[]=;
register int i,x,l;
while(hd<=tl)
for(x=q[hd++],vis[x]=,i=,l=to[x].size();i<l;++i)
if(dis[to[x][i].to]>dis[x]+to[x][i].val)
{
dis[to[x][i].to]=dis[x]+to[x][i].val;
if(!vis[to[x][i].to])vis[to[x][i].to]=,q[++tl]=to[x][i].to;
}
dfs0();
}
inline void dfs1(int rt,int fa)
{
size[rt]=,maxs[rt]=;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].to]&&s[i].to!=fa)
dfs1(s[i].to,rt),size[rt]+=size[s[i].to],maxs[rt]=max(maxs[rt],size[s[i].to]);
maxs[rt]=max(maxs[rt],totsize-size[rt]);
if(maxs[rt]<maxs[root])root=rt;
}
inline void dfs2(int rt,int fa,int dp)
{
if(maxl[k-dp-]!=-inf)
if(ans<dis[rt]+maxl[k-dp-])ans=dis[rt]+maxl[k-dp-],tot=cnt[k-dp-];
else if(ans==dis[rt]+maxl[k-dp-])tot+=cnt[k-dp-];
if(dp+<k)for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].to]&&s[i].to!=fa)
dis[s[i].to]=dis[rt]+s[i].val,dfs2(s[i].to,rt,dp+);
}
inline void update(int rt,int fa,int dp)
{
if(maxl[dp]<dis[rt])maxl[dp]=dis[rt],cnt[dp]=;
else if(maxl[dp]==dis[rt])++cnt[dp];
if(dp+<k)for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].to]&&s[i].to!=fa)
update(s[i].to,rt,dp+);
}
inline void solve(int rt)
{
vis[rt]=,maxl[]=,cnt[]=;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].to])dis[s[i].to]=s[i].val,dfs2(s[i].to,,),update(s[i].to,,);
for(int i=;i<=k;++i)maxl[i]=-inf,cnt[i]=;
// for(int i=adj[rt];i;i=s[i].next)if(!vis[s[i].to])update(s[i].to,0,1,0);
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].to]&&size[s[i].to]>=k)root=,totsize=size[s[i].to],dfs1(s[i].to,),solve(root);
}
inline void work_and_print()
{
memset(vis,,sizeof(vis)),memset(dis,,sizeof(dis)),memset(cnt,,sizeof(cnt));
for(int i=;i<=k;++i)maxl[i]=-inf;
totsize=n,root=,maxs[]=inf,dfs1(,),solve(root);
printf("%d %d",ans,tot);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
register int i,j,l,a,b,c;
for(i=;i<=m;++i)
scanf("%d%d%d",&a,&b,&c),to[a].push_back((node){b,c}),to[b].push_back((node){a,c});
for(i=;i<=n;++i)sort(to[i].begin(),to[i].end(),mt);
spfa_and_build(),work_and_print();
}

BZOJ4016

bzoj3672 (推式子,点分治+CDQ维护凸壳)

bzoj1758 (01分数规划,卡精卡时……)

二.从点分治到动态点分治

做着做着题,你可能会发现有的题目要修改……这可怎么办呢。

我们肯定不能一次修改跑一次DP或者点分治吧……
那么我们考虑用数组或者数据结构来维护我们需要的信息,每一次修改都维护数据结构中对应的信息。
这其实借用了树规中的思想,我们通过维护这些信息,可以实现点之间$O(1)$或者$O(logn)$的转移。
可是如果我们对原树中的点维护这些数据结构,一条链就可以送我们上天……
但同时我们观察到,如果我们把点分治时每一层的重心之间连边,这就构成了一颗高度为$logn$的新树,我们叫它分治树。
那么我们就用数据结构维护分治树种对应我们需要的信息。
一个常用的思想:维护当前子树中到父亲的某些信息,从而减小查询的复杂度……
这样说可能有些抽象……我们来看道题:

1095: [ZJOI2007]Hide 捉迷藏

Time Limit: 40 Sec  Memory Limit: 256 MB

Description

  捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩
捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋
子都互相可达。游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的
时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要
求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两
个孩子的距离(即最远的两个关灯房间的距离)。 我们将以如下形式定义每一种操作: C(hange) i 改变第i个房
间的照明状态,若原来打开,则关闭;若原来关闭,则打开。 G(ame) 开始一次游戏,查询最远的两个关灯房间的
距离。

Input

  第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。接下来N-1行每行两个整数a, b,
表示房间a与房间b之间有一条走廊相连。接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如
上文所示。

Output

  对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关
着灯的,输出0;若所有房间的灯都开着,输出-1。

Sample Input

8
1 2
2 3
3 4
3 5
3 6
6 7
6 8
7
G
C 1
G
C 2
G
C 1
G

Sample Output

4
3
3
4

HINT

对于100%的数据, N ≤100000, M ≤500000。

那么我们看这个题,如果这个题不用修改的话,$O(n)$的树规和$O(nlogn)$的点分治都可以解决。

想一下,我们需要什么变量呢?需要最长链和次长链拼起来对吧……

那么我们可以用一些数据结构维护每个点在分治树中伸下去与子树中的每个黑点之间链的长度,

这个数据结构我们可以用大根堆,那么最长链就是堆顶了。

但同时,我们意识到最长链和次长链不能来自同一棵分治树下的子树,

因此我们再来一个数据结构,维护这个点每个分治树儿子的堆顶(这就是分治树中“维护父亲/维护孩子”思想的体现,虽然不是很明显),我们还可以用个堆。

那么这个堆的前两个元素之和就是经过这个点的最长链长度,我们对于每个点都可以维护这样一个变量(当然,也可能不存在)。

那么我们再用一个大根堆维护每个节点的答案,那么这个堆的堆顶就是答案了。

每次修改的时候,我们在第一类堆中删除或者添加对应的链长,维护第二类堆对应子树的元素,再更新第三类堆中的答案,就实现了一次维护。

分治树的深度是$logn$的,堆操作的是$logn$的,因此时间复杂度为$O(nlog^{2}n)$。

代码实现:

 #include <cstdio>
#include <cstring>
#include <ctime>
#include <set>
#include <queue>
using namespace std;
#define N 100010
#define inf 0x3fffffff
#define Vt Vater[rt]
int n,e,adj[N];
struct edge{int zhong,next;}s[N<<];
inline void add(int qi,int zhong)
{s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;}
int Vater[N],size[N],root,totsize,maxs[N];
bool state[N],vis[N];
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
struct heap
{
priority_queue<int>q1,q2;
inline void push(int x){q1.push(x);}
inline void erase(int x){q2.push(x);}
inline int top()
{
while(q2.size()&&q1.top()==q2.top())q1.pop(),q2.pop();
return q1.top();
}
inline void pop()
{
while(q2.size()&&q1.top()==q2.top())q1.pop(),q2.pop();
q1.pop();
}
inline int top2()
{
int val=top();pop();
int ret=top();push(val);
return ret;
}
inline int size()
{
return q1.size()-q2.size();
}
}h1[N],h2[N],h3;
inline void dfs1(int rt,int fa)
{
size[rt]=,maxs[rt]=;
for(int i=adj[rt];i;i=s[i].next)
if(s[i].zhong!=fa&&!vis[s[i].zhong])
dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong],
maxs[rt]=max(maxs[rt],size[s[i].zhong]);
maxs[rt]=max(maxs[rt],totsize-maxs[rt]);
if(maxs[rt]<maxs[root])root=rt;
}
int f[N][],bin[],tp,deep[N];
inline void pre(int rt,int fa)
{
f[rt][]=fa;deep[rt]=deep[fa]+;
for(int i=;bin[i]+<=deep[rt];++i)f[rt][i]=f[f[rt][i-]][i-];
for(int i=adj[rt];i;i=s[i].next)
if(s[i].zhong!=fa)pre(s[i].zhong,rt);
}
inline int LCA(int a,int b)
{
if(deep[a]<deep[b])a^=b,b^=a,a^=b;
int i,cha=deep[a]-deep[b];
for(i=tp;~i;--i)if(cha&bin[i])a=f[a][i];
if(a==b)return a;
for(i=tp;~i;--i)
if(f[a][i]!=f[b][i])a=f[a][i],b=f[b][i];
return f[a][];
}
inline int dis(int a,int b)
{return deep[a]+deep[b]-(deep[LCA(a,b)]<<);}
inline void dfs3(int rt,int fa,int Vatty)
{
h1[root].push(dis(rt,Vatty));
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong]&&s[i].zhong!=fa)
dfs3(s[i].zhong,rt,Vatty);
}
inline void dfs2(int rt,int fa)
{
Vt=fa,vis[rt]=,h2[rt].push();
int siz=totsize;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong])
{
if(size[s[i].zhong]>size[rt])
totsize=siz-size[rt];
else
totsize=size[s[i].zhong];
root=,dfs1(s[i].zhong,),dfs3(root,,rt);
h2[rt].push(h1[root].top()),dfs2(root,rt);
}
if(h2[rt].size()>)h3.push(h2[rt].top()+h2[rt].top2());
}
inline void turnoff(int who)
{
int val,tmp;
if(h2[who].size()>)h3.erase(h2[who].top()+h2[who].top2());
h2[who].push();
if(h2[who].size()>)h3.push(h2[who].top()+h2[who].top2());
//queue empty() 后依然有数
for(int rt=who;Vt;rt=Vt)
{
if(h2[Vt].size()>)h3.erase(h2[Vt].top()+h2[Vt].top2());
if(h1[rt].size())h2[Vt].erase(h1[rt].top());
h1[rt].push(dis(who,Vt));
h2[Vt].push(h1[rt].top());
if(h2[Vt].size()>)h3.push(h2[Vt].top()+h2[Vt].top2());
}
}
inline void turnon(int who)
{
int val,tmp;
if(h2[who].size()>)h3.erase(h2[who].top()+h2[who].top2());
h2[who].erase();
if(h2[who].size()>)h3.push(h2[who].top()+h2[who].top2());
//queue empty()后依然有数
for(int rt=who;Vt;rt=Vt)
{
if(h2[Vt].size()>)h3.erase(h2[Vt].top()+h2[Vt].top2());
h2[Vt].erase(h1[rt].top());
h1[rt].erase(dis(who,Vt));
if(h1[rt].size())h2[Vt].push(h1[rt].top());
if(h2[Vt].size()>)h3.push(h2[Vt].top()+h2[Vt].top2());
}
}
char B[<<],X=,*S=B,*T=B;
#define getc ( S==T&&( T=(S=B)+fread(B,1,1<<15,stdin),S==T )?0:*S++ )
inline int read()
{
int x=;while(X<''||X>'')X=getc;
while(X>=''&&X<='')x=*x+(X^),X=getc;
return x;
}
inline void readc(){X=getc;while(X<'A'||X>'Z')X=getc;}
int main()
{
// freopen("hide1.in","r",stdin);
// freopen("hide.out","w",stdout);
n=read();
register int i,j,q,a,b,cnt=n;
for(bin[]=i=;i<=;++i)bin[i]=bin[i-]<<;
while(bin[tp+]<=n)++tp;
for(i=;i<n;++i)
a=read(),b=read(),add(a,b),add(b,a);
pre(,);
maxs[]=inf,root=,totsize=n,dfs1(,),dfs2(root,);
q=read();
while(q--)
{
readc();
if(X=='C')
{
i=read();
if(state[i])++cnt,turnoff(i);
else --cnt,turnon(i);
state[i]^=;
}
else
{
if(cnt<)printf("%d\n",cnt-);
else printf("%d\n",h3.top());
}
}
}

BZOJ1095

其实这道题是比较简单的……那么我们再看一道题。

3924: [Zjoi2015]幻想乡战略游戏

Time Limit: 100 Sec  Memory Limit: 256 MB

Description

傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点u上,并且空地v上有dv个单位的军队,那么幽香每天就要花费dv×dist(u,v)的金钱来补给这些军队。由于幽香需要补给所有的军队,因此幽香总共就要花费为Sigma(Dv*dist(u,v),其中1<=V<=N)的代价。其中dist(u,v)表示u个v在树上的距离(唯一路径的权和)。 因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

PDF版试题:JudgeOnline/upload/201708/zjoi2015d1.pdf

Input

第一行两个数n和Q分别表示树的点数和幽香操作的个数,其中点从1到n标号。 
接下来n-1行,每行三个正整数a,b,c,表示a和b之间有一条边权为c的边。 
接下来Q行,每行两个数u,e,表示幽香在点u上放了e单位个军队
(如果e<0,就相当于是幽香在u上减少了|e|单位个军队,说白了就是du←du+e)。
数据保证任何时刻每个点上的军队数量都是非负的。 
1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5
对于所有数据,这个树上所有点的度数都不超过20
N,Q>=1

Output

对于幽香的每个操作,输出操作完成以后,每天的最小花费,也即如果幽香选择最优的补给点进行补给时的花费。

Sample Input

10 5
1 2 1
2 3 1
2 4 1
1 5 1
2 61
2 7 1
5 8 1
7 91
1 10 1
3 1
2 1
8 1
3 1
4 1

Sample Output

0
1
4
5
6
怎么操作呢?我们不难发现,由于目标函数是$Σd_{v}*dis(u,v)$,由定义可知,我们的目标其实就是求带权的重心
那么怎么求呢……还是先假设我们有一棵静态的树,那么考虑树规。
想一下树规中我们都求了什么:
设$s1[i]$为i子树点权和,$s2[i]$为i子树点到它的距离和,$s3[i]$为i父亲到它的距离和。
那么转移应该是
$ans[i]=ans[fa]+s2[fa]-s2[i]-s1[i]*dis(i,fa)+s3[fa]+(sum-s1[i])*dis(i,fa)$
dis我们可以用ST表RMQ或者倍增LCA来求.
但其实我们是可以不要s3数组,一层一层跳着往上求$s2[fa]-s2[i]-s1[i]*dis(i,fa)$这个式子的。
由于原树可能是条链,我们不敢直接跳,只好维护s3数组。
但是如果修改的话,s3涉及到的修改会特别多!
我们联想到,分治树的高度是$logn$的!
因此我们可以不维护s3,仅仅维护s1和s2,在分治树上用$O(logn)$的时间求出某一个点作为带权重心时的权和。
但是你会发现,我们上面的式子里有一个-s2[i]很难处理:在树规中i和fa只隔了一条边,但是在分治树中,你并不知道元素长什么样子!
因此我们还是得加一个s3数组,s3[i]表示i的分治树子节点到i分治树父亲的权和。这就是我们动态树分治中“维护父亲”思想的典型体现
那么我们现在就可以以$O(logn)$的复杂度枚举i的祖先fa,求$Σs2[fa]-s3[i]+(s1[fa]-s1[i])*dis(i,fa)$来求与i在原树上的lca是fa的点到i的距离了。
*这种$O(logn)$跳父亲的做法也是一种常见的思想。
然后对于统计答案,我们可以每次从分治树的根出发,看往他的哪个儿子走答案更优。
我们最多走$logn$层,每一层最多进行20次$O(logn)$的计算,因此查询是$O(20log^{2}n)$的.
总的复杂度$O(20Qlog^{2}n)$。代码见下:
 #include <cstdio>
#include <cstring>
using namespace std;
#define LL long long
#define N 100010
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
struct edge{int zhong,next,val;};
struct G
{
edge s[N<<];int e,adj[N];
G(){e=;memset(adj,,sizeof(adj));}
inline void add(int qi,int zhong,int val=)
{s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;s[e].val=val;}
}T1,T2;
int n,q,f[N<<][],logn[N<<],bin[],tp;
LL s[][N],sum,ans,len[N];
int dfn[N],num;
inline void dfs1(int rt,int fa)
{
f[(dfn[rt]=++num)][]=len[rt];
for(int i=T1.adj[rt];i;i=T1.s[i].next)
if(T1.s[i].zhong!=fa)
len[T1.s[i].zhong]=len[rt]+T1.s[i].val,dfs1(T1.s[i].zhong,rt),f[++num][]=len[rt];
}
inline LL LCA(int a,int b)
{
if(dfn[a]>dfn[b])a^=b,b^=a,a^=b;
int k=logn[dfn[b]-dfn[a]+];
return min(f[dfn[a]][k],f[dfn[b]-bin[k]+][k])<<;
}
inline LL dis(int a,int b){return len[a]+len[b]-LCA(a,b);}
int size[N],maxs[N],totsize,root,Vater[N];
bool vis[N];
inline void dfs2(int rt,int fa)
{
size[rt]=,maxs[rt]=;
for(int i=T1.adj[rt];i;i=T1.s[i].next)
if(!vis[T1.s[i].zhong]&&T1.s[i].zhong!=fa)
dfs2(T1.s[i].zhong,rt),size[rt]+=size[T1.s[i].zhong],
maxs[rt]=max(size[T1.s[i].zhong],maxs[rt]);
maxs[rt]=max(totsize-size[rt],maxs[rt]);
if(maxs[rt]<maxs[root])root=rt;
}
inline void dfs3(int rt,int fa)
{
Vater[rt]=fa;vis[rt]=;int siz=totsize;
for(int i=T1.adj[rt];i;i=T1.s[i].next)
if(!vis[T1.s[i].zhong])
{
if(size[T1.s[i].zhong]>size[rt])totsize=siz-size[rt];
else totsize=size[T1.s[i].zhong];
root=,dfs2(T1.s[i].zhong,),T2.add(rt,root,T1.s[i].zhong),dfs3(root,rt);
}
}
inline void update(int who,LL val)
{
for(int rt=who;rt;rt=Vater[rt])
{
s[][rt]+=val,s[][rt]+=dis(rt,who)*val;
if(Vater[rt])s[][rt]+=dis(Vater[rt],who)*val;
}
}
inline LL calc(int rt)
{
LL ret=;
for(int x=rt;x;x=Vater[x])
{
ret+=s[][x];
if(Vater[x])ret+=-s[][x]+(s[][Vater[x]]-s[][x])*dis(Vater[x],rt);
}
return ret;
}
inline LL getans(int rt)
{
LL temp=calc(rt);
for(int i=T2.adj[rt];i;i=T2.s[i].next)
if(calc(T2.s[i].val)<temp)return getans(T2.s[i].zhong);//*******
return temp;
}
char B[<<],*S=B,*T=B;
#define getc ( S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
inline int read()
{
int x=,f=;register char c=getc;
while(c<''||c>''){if(c=='-')f=-;c=getc;}
while(c>=''&&c<='')x=*x+(c^),c=getc;
return x*f;
}
int main()
{
register int i,j,orig,a,b,c;
n=read();q=read();
for(bin[]=i=;i<=;++i)bin[i]=bin[i-]<<;
while(bin[tp+]<=(n<<))++tp;
for(logn[]=-,i=;i<=(n<<);++i)logn[i]=logn[i>>]+;
for(i=;i<n;++i)
a=read(),b=read(),c=read(),T1.add(a,b,c),T1.add(b,a,c);
dfs1(,),root=,maxs[]=n+,totsize=n,dfs2(,);
for(i=;i<=tp;++i)
for(j=;j+bin[i]-<=(n<<);++j)
f[j][i]=min(f[j][i-],f[j+bin[i-]][i-]);
orig=root,dfs3(root,);
while(q--)
a=read(),b=read(),update(a,b),printf("%lld\n",getans(orig));
}

BZOJ4012

我们再来一道大同小异的题目:

4012: [HNOI2015]开店

Time Limit: 70 Sec  Memory Limit: 512 MB

Description

风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到

人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。这样的
想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面
向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有 n
个地方,编号为 1 到 n,被 n-1 条带权的边连接起来。每个地方都住着一个妖怪,
其中第 i 个地方的妖怪年龄是 x_i。妖怪都是些比较喜欢安静的家伙,所以它们并
不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于 3。妖怪和人一
样,兴趣点随着年龄的变化自然就会变化,比如我们的 18 岁少女幽香和八云紫就
比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以
幽香打算选择一个地方 u(u为编号),然后在 u开一家面向年龄在 L到R 之间(即
年龄大于等于 L、小于等于 R)的妖怪的店。也有可能 u这个地方离这些妖怪比较
远,于是幽香就想要知道所有年龄在 L 到 R 之间的妖怪,到点 u 的距离的和是多
少(妖怪到 u 的距离是该妖怪所在地方到 u 的路径上的边的权之和) ,幽香把这个
称为这个开店方案的方便值。幽香她们还没有决定要把店开在哪里,八云紫倒是准
备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。

Input

第一行三个用空格分开的数 n、Q和A,表示树的大小、开店的方案个数和妖

怪的年龄上限。 
第二行n个用空格分开的数 x_1、x_2、…、x_n,x_i 表示第i 个地点妖怪的年
龄,满足0<=x_i<A。(年龄是可以为 0的,例如刚出生的妖怪的年龄为 0。) 
接下来 n-1 行,每行三个用空格分开的数 a、b、c,表示树上的顶点 a 和 b 之
间有一条权为c(1 <= c <= 1000)的边,a和b 是顶点编号。 
接下来Q行,每行三个用空格分开的数 u、 a、 b。对于这 Q行的每一行,用 a、
b、A计算出 L和R,表示询问“在地方 u开店,面向妖怪的年龄区间为[L,R]的方
案的方便值是多少”。对于其中第 1 行,L 和 R 的计算方法为:L=min(a%A,b%A), 
R=max(a%A,b%A)。对于第 2到第 Q行,假设前一行得到的方便值为 ans,那么当
前行的 L 和 R 计算方法为: L=min((a+ans)%A,(b+ans)%A), 
R=max((a+ans)%A,(b+ans)%A)。 

Output

对于每个方案,输出一行表示方便值。

Sample Input

10 10 10
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4

Sample Output

1603
957
7161
9466
3232
5223
1879
1669
1282
0

HINT

满足 n<=150000,Q<=200000。对于所有数据,满足 A<=10^9

现在你应该能想到怎么做了吧!

我们用数据结构维护以i为分治树根的子树每个年龄的妖怪到i的距离,以及到i分治树父亲的距离,然后用数据结构求和。

数据结构用什么呢?线段树?$O(nlog^{2}n)$的空间复杂度肯定会MLE的。

注意到这个求和可以写为前缀和相减的形式,因此我们用可以用$O(nlogn)$的空间复杂度的vector+sort排序节省内存。

那么这道题就被解决啦……代码见下:

 #include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define N 150010
#define LL long long
int n,e,tot,adj[N],q,maxn,val[N];
char B[<<],*S=B,*T=B;
#define getc (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
inline int read()
{
int x=;register char c=getc;
while(c<''||c>'')c=getc;
while(c>=''&&c<='')x=*x+(c^),c=getc;
return x;
}
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
struct edge{int zhong,next,val;}s[N<<];
inline void add(int qi,int zhong,int val)
{s[++e].zhong=zhong,s[e].val=val,s[e].next=adj[qi],adj[qi]=e;}
int f[N<<][],deep[N],dfn[N],num,bin[],tp,logn[N<<];
inline void ST()
{
for(int i=;i<=tp;++i)
for(int j=;j+bin[i]-<=(n<<);++j)
f[j][i]=min(f[j][i-],f[j+bin[i-]][i-]);
}
inline void dfs1(int rt,int fa,int len)
{
f[(dfn[rt]=++num)][]=deep[rt]=len;
for(int i=adj[rt];i;i=s[i].next)
if(s[i].zhong!=fa)
dfs1(s[i].zhong,rt,len+s[i].val),f[++num][]=len;
}
int Vater[N],size[N],maxs[N],totsize,root;
bool vis[N];
inline void dfs2(int rt,int fa)
{
size[rt]=,maxs[rt]=;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong]&&s[i].zhong!=fa)
dfs2(s[i].zhong,rt),size[rt]+=size[s[i].zhong],
maxs[rt]=max(maxs[rt],size[s[i].zhong]);
maxs[rt]=max(maxs[rt],totsize-size[rt]);
if(maxs[rt]<maxs[root])root=rt;
}
inline LL dis(int a,int b)
{
if(dfn[a]>dfn[b])a^=b,b^=a,a^=b;
int k=logn[dfn[b]-dfn[a]+];
return deep[a]+deep[b]-(min(f[dfn[a]][k],f[dfn[b]-bin[k]+][k])<<);
}
struct node
{
int val;LL size[];
node(int a=,LL b=,LL c=,LL d=){val=a,size[]=b,size[]=c,size[]=d;}
inline bool operator < (const node &b)const
{return val<b.val;}
};
vector<node>sta[N];
inline void dfs3(int rt,int fa,int Vatti)
{
sta[Vatti].push_back(node(val[rt],,dis(rt,Vatti), Vater[Vatti]?dis(rt,Vater[Vatti]): ));
for(int i=adj[rt];i;i=s[i].next)
if(s[i].zhong!=fa&&!vis[s[i].zhong])
dfs3(s[i].zhong,rt,Vatti);
}
inline void dfs4(int rt,int fa)
{
Vater[rt]=fa,vis[rt]=;
int siz=totsize;
dfs3(rt,,rt);sta[rt].push_back(node(-,,,));
sort(sta[rt].begin(),sta[rt].end());
for(int i=,j=sta[rt].size();i<j-;++i)
sta[rt][i+].size[]+=sta[rt][i].size[],
sta[rt][i+].size[]+=sta[rt][i].size[],
sta[rt][i+].size[]+=sta[rt][i].size[];
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong])
{
if(size[s[i].zhong]>size[rt])totsize=siz-size[rt];
else totsize=size[s[i].zhong];
root=,dfs2(s[i].zhong,),dfs4(root,rt);
}
}
inline node query(int id,int l,int r)
{
if(id==)return node();
vector<node>::iterator it1=upper_bound(sta[id].begin(),sta[id].end(),node(r,,,) );--it1;
vector<node>::iterator it2=upper_bound(sta[id].begin(),sta[id].end(),node(l-,,,));--it2;
return node(,it1->size[]-it2->size[],it1->size[]-it2->size[],it1->size[]-it2->size[]);
}
inline LL calc(int rt,int l,int r)
{
LL ret=;
// printf("rt=%d l=%d r=%d\n",rt,l,r);
for(int x=rt;x;x=Vater[x])
{
node a=query(x,l,r);
ret+=a.size[];
if(x!=rt)ret+=a.size[]*dis(x,rt);
if(Vater[x])ret-=a.size[]+a.size[]*dis(Vater[x],rt);
}
return ret;
}
int main()
{
register int i,j,a,b,c;LL ans=;
n=read(),q=read(),maxn=read();
for(bin[]=i=;i<=;++i)bin[i]=bin[i-]<<;
while(bin[tp+]<=(n<<))++tp;
for(logn[]=-,i=;i<=(n<<);++i)logn[i]=logn[i>>]+;
for(i=;i<=n;++i)val[i]=read();
for(i=;i<n;++i)
a=read(),b=read(),c=read(),add(a,b,c),add(b,a,c);
dfs1(,,),ST();
root=,maxs[]=n+,totsize=n,dfs2(,);
dfs4(root,);
while(q--)
{
a=read(),b=read(),c=read();
b=(b+ans)%maxn,c=(c+ans)%maxn;
if(b>c)b^=c,c^=b,b^=c;
printf("%lld\n",ans=calc(a,b,c));
}
}

BZOJ4012

最后我们来个大boss试试知难♂而上

3435: [Wc2014]紫荆花之恋

Time Limit: 240 Sec  Memory Limit: 512 MB

Description

强强和萌萌是一对好朋友。有一天他们在外面闲逛,突然看到前方有一棵紫荆树。这已经是紫荆花飞舞的季节了,无数的花瓣以肉眼可见的速度从紫荆树上长了出来。仔细看看的话,这个大树实际上是一个带权树。每个时刻它会长出一个新的叶子节点。每个节点上有一个可爱的小精灵,新长出的节点上也会同时出现一个新的小精灵。小精灵是很萌但是也很脆弱的生物,每个小精灵 i 都有一个感受能力值Ri ,小精灵 i, j 成为朋友当且仅当在树上 i 和 j 的距离 dist(i,j) ≤ Ri + R! ,其中 dist(i, j)表示在这个树上从 i 到 j 的唯一路径上所有边的边权和。强强和萌萌很好奇每次新长出一个叶子节点之后,这个树上总共有几对朋友。  
我们假定这个树一开始为空,节点按照加入的顺序从 1开始编号。由于强强非常好奇, 你必须在他每次出现新节点后马上给出总共的朋友对数,不能拖延哦。

Input

共有 n + 2 行。 
第一行包含一个正整数,表示测试点编号。 
第二行包含一个正整数 n ,表示总共要加入的节点数。 
我们令加入节点前的总共朋友对数是 last_ans,在一开始时它的值为0。 
接下来 n 行中第 i 行有三个数 ai, bi, ri,表示节点  i  的父节点的编号为 ai xor (last_ans mod 10^9)   (其中xor 表示异或,mod  表示取余,数据保证这样操作后得到的结果介于 1到i  –  1之间),与父节点之间的边权为 ci,节点 i 上小精灵的感受能力值为r!。 
注意 a1 = c1 = 0,表示 1 号点是根节点,对于 i > 1,父节点的编号至少为1。

Output

包含 n 行,每行输出1 个整数, 表示加入第 i 个点之后,树上有几对朋友。

Sample Input

0
5
0 0 6
1 2 4
0 9 4
0 5 5
0 2 4 

Sample Output

0
1
2
4

HINT

1<=Ci<=10000
Ai<=2*10^9
Ri<=10^9
N<=100000

那么这题怎么做呢……

我们先考虑把题设式子变形:

\[dis(u,v)<=r_{u}+r_{v}  \]

\[dis(u,lca)+dis(lca,v)<=r_{u}+r_{v}  \]

\[r_{u}-dis(lca,u)>=dis(lca,v)-r_{v}\]

那么接下来我们沿用上面几题的思想,在原树上每个节点开2棵平衡树,维护以i为根的子树中的$dis(i,u)-r_{u}$值和$dis(fa[i],u)-r_{u}$值。

这样每次我们从新插入的节点开始往上爬到根,并且更新答案即可。

但是我们发现,如果这是条链……时间复杂度会被卡到$O(n^{2})$

但是我们转念一想,点分树是相对平衡的,深度是$logn$级别的呀!

所以我们借用替罪羊拍扁重建的思想,如果在插入结束后,

某个节点的某个儿子特别大(用$alpha$判断),我们就把那棵子树重构为一棵分治树。

这样我们的复杂度可以有$O(nlog^{2}n)$的保障。

在代码实现的时候,要特别注意重构时和原来这个子树的分治树父亲之间信息的维护。

代码:

 #include <cstdio>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;
#define LL long long
#define inf 1000000000
#define N 100010
#define alpha 0.755
int n,e,adj[N],val[N];
struct edge{int zhong,next;}s[N<<];
inline void add(int qi,int zhong)
{s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;}
vector<int> to[N];
int f[N][],bin[],tp,deep[N],len[N];
inline int LCA(int a,int b)
{
if(deep[a]<deep[b])a^=b,b^=a,a^=b;
int i,cha=deep[a]-deep[b];
for(i=tp;~i;--i)if(cha&bin[i])a=f[a][i];
if(a==b)return a;
for(i=tp;~i;--i)if(f[a][i]!=f[b][i])a=f[a][i],b=f[b][i];
return f[a][];
}
inline int dis(int a,int b){return len[a]+len[b]-len[LCA(a,b)]*;}
struct Goat
{
int val,size;Goat *ch[];
Goat(){}
inline bool bad()
{return ch[]->size>=size*alpha+ || ch[]->size>=size*alpha+; }
}*tree1[N],*tree2[N],mem[N<<],*pool[N<<],*null,*sta[N];
int tot,top;
inline void intn()
{
null=new Goat();
null->ch[]=null->ch[]=null,null->val=null->size=;
for(int i=;i<(N<<);++i)pool[i]=mem+i;
tot=(N<<)-;
for(int i=;i<=n;++i)tree1[i]=tree2[i]=null;
}
inline Goat** insert(Goat *&a,int val)
{
if(a==null)
{
a=pool[tot--],a->ch[]=a->ch[]=null;
a->val=val,a->size=;return &null;
}
++a->size;
Goat **o=insert(a->ch[a->val<val],val);
if(a->bad())o=&a;return o;
}
inline int get_rank(Goat *o,int val)
{
if(o==null)return ;
return (o->val>=val)?get_rank(o->ch[],val):(get_rank(o->ch[],val)+o->ch[]->size+);
}
inline void Erholung(Goat *o)
{
if(o==null)return;
if(o->ch[]!=null)Erholung(o->ch[]);
pool[++tot]=o;
if(o->ch[]!=null)Erholung(o->ch[]);
}
inline void travel(Goat *o)
{
if(o==null)return;
if(o->ch[]!=null)travel(o->ch[]);
sta[++top]=o;
if(o->ch[]!=null)travel(o->ch[]);
}
inline Goat* build(int l,int r)
{
if(l>r)return null;
int mi=l+r>>;
Goat *o=sta[mi];o->size=r-l+;
o->ch[]=build(l,mi-),o->ch[]=build(mi+,r);
return o;
}
inline void rebuild(Goat *&o){top=,travel(o),o=build(,top);}
inline void Insert(Goat *&a,int val)
{
Goat **o=insert(a,val);
if(*o!=null)rebuild(*o);
}
int size[N],maxs[N],totsize,root,Vater[N];
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
bool vis[N];
inline void dfs1(int rt,int fa)
{
size[rt]=,maxs[rt]=;
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong]&&s[i].zhong!=fa)
dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong],
maxs[rt]=max(maxs[rt],size[s[i].zhong]);
maxs[rt]=max(maxs[rt],totsize-size[rt]);
if(maxs[rt]<maxs[root])root=rt;
}
inline void dfs2(int rt,int fa,int Vatti)
{
Insert(tree1[Vatti],dis(rt,Vatti)-val[rt]);
if(Vater[Vatti])Insert(tree2[Vatti],dis(rt,Vater[Vatti])-val[rt]);
for(int i=adj[rt];i;i=s[i].next)
if(s[i].zhong!=fa&&!vis[s[i].zhong])
dfs2(s[i].zhong,rt,Vatti);
}
inline void dfs3(int rt,int fa)
{
Vater[rt]=fa,vis[rt]=;
int siz=totsize;
dfs2(rt,,rt);
for(int i=adj[rt];i;i=s[i].next)
if(!vis[s[i].zhong])
{
if(size[s[i].zhong]>size[rt])totsize=siz-size[rt];
else totsize=size[s[i].zhong];
root=,dfs1(s[i].zhong,rt),
to[rt].push_back(root),dfs3(root,rt);
}
}
inline void recover(int x)
{
++totsize,vis[x]=,
Erholung(tree1[x]),Erholung(tree2[x]),
tree1[x]=tree2[x]=null;
for(int i=,k=to[x].size();i<k;++i)recover(to[x][i]);
to[x].clear();
}
inline void rebuild(int x)
{
totsize=,recover(x),root=,dfs1(x,);
if(Vater[x])for(int i=,j=to[Vater[x]].size();i<j;++i)
if(to[Vater[x]][i]==x)to[Vater[x]][i]=root;
dfs3(root,Vater[x]);
}
LL ans=;
inline int insert(int x)
{
int rt,ds,ret=;
for(rt=x;Vater[rt];rt=Vater[rt])
ds=val[x]-dis(x,Vater[rt])+,ans+=get_rank(tree1[Vater[rt]],ds)-get_rank(tree2[rt],ds);
for(rt=x;rt;rt=Vater[rt])
{
Insert(tree1[rt],dis(x,rt)-val[x]);
if(Vater[rt])Insert(tree2[rt],dis(x,Vater[rt])-val[x]);
}
for(rt=x;Vater[rt];rt=Vater[rt])
if(tree1[rt]->size>=tree1[Vater[rt]]->size*alpha+)ret=Vater[rt];//*****
return ret;
}b
char B[<<],*S=B,*T=B;
#define getc (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
inline int read()
{
int x=;register char c=getc;
while(c<''||c>'')c=getc;
while(c>=''&&c<='')x=*x+(c^),c=getc;
return x;
}
int main()
{
n=read(),n=read();
register int i,j,x,b;
for(bin[]=i=;i<=;++i)bin[i]=bin[i-]<<;
while(bin[tp+]<=n)++tp;
maxs[]=inf,root=,intn();
for(i=;i<=n;++i)
{
Vater[i]=f[i][]=read()^(ans%inf),b=read(),val[i]=read();
deep[i]=deep[f[i][]]+,len[i]=len[f[i][]]+b;vis[i]=;
if(Vater[i])to[Vater[i]].push_back(i),add(f[i][],i),add(i,f[i][]);
for(int j=;bin[j]+<=deep[i];++j)f[i][j]=f[f[i][j-]][j-];
x=insert(i);if(x)rebuild(x);
printf("%lld\n",ans);
}
}

BZOJ3435

三.总结

点分治和动态树分治其实是对树规思想的变形。我们利用分治树高度很小的优点,对每个点维护相应的信息,

通过$O(logn)$的额外复杂度来进行普通树规无法完成的操作。希望本文能对你有帮助!