(写篇博客证明自己还活着×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
Input
Output
Sample Input
1 2 1
1 3 2
1 4 1
2 5 3
Sample Output
【样例说明】
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。
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点在有根树中的父亲)
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
#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分数规划,卡精卡时……)
二.从点分治到动态点分治
做着做着题,你可能会发现有的题目要修改……这可怎么办呢。
这其实借用了树规中的思想,我们通过维护这些信息,可以实现点之间$O(1)$或者$O(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
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
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在树上的距离(唯一路径的权和)。 因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。
Input
Output
对于幽香的每个操作,输出操作完成以后,每天的最小花费,也即如果幽香选择最优的补给点进行补给时的花费。
Sample Input
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
1
4
5
6
#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
风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到
Input
第一行三个用空格分开的数 n、Q和A,表示树的大小、开店的方案个数和妖
Output
对于每个方案,输出一行表示方便值。
Sample Input
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
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
5
0 0 6
1 2 4
0 9 4
0 5 5
0 2 4
Sample Output
1
2
4
7
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)$的额外复杂度来进行普通树规无法完成的操作。希望本文能对你有帮助!