例行的废话:
为什么有这么个东西?
因为沙茶博主没有好好学过点分治,NOIP前讲了几次都是看着题解瞎打了一气混过去的,NOIP之后就没有好好讲的也没有好好做了。
所以点分治水平基本为零,于是尝试在滚粗之前抢救一下,重学点分治(和动态点分治)。
注意是自我抢救,不是教程或者题目汇总,所以完全不建议看这个东西(不会的看了还不会,会了的看了发现都是水题,而且很可能错误百出)
(什么,边分治?那更没学啊)
废话结束
网上好多的点分治题解都是说:找到重心,处理经过重心的路径,继续递归处理各个子树
然后我NOIP前看这个:怎么处理啊?
于是就咕咕了,于是就没学,于是只能省选前一个月才学点分治
我佛了,这人怎么这么沙茶
废话结束
关于重心就不提了,说一说一般怎么“处理经过重心的路径”,点分治要说怎么写就是上面那几句,主要是你要写出来
先从重心开始DFS一遍,把到当前联通块内所有点的路径(一般都是距离好吧=。=)存起来
然后处理的话根据具体题目有两种方法,第一种是容斥子树
举个例子
洛谷 P4178 Tree
求树上长度不超过k的路径数
然后我们现在搞出来了重心到当前联通块里所有点的距离
显然我们排序后双指针卡一下统计答案就行了,复杂度$O(nlog^2 n)$
就行了......吗?
不对,可能我们把一个子树里的两条链给统计进去了。。。。。。
所以我们到每个子树再统计一遍,这时不走父边,但是要算父边的长度(因为你要容斥掉从父亲那里统计到的答案),容斥掉这部分答案
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=40005; int n,k,t1,t2,t3,pt,cnt,tot,cor,ans,maxs; int p[N],noww[2*N],goal[2*N],val[2*N]; int vis[N],siz[N],dis[N],vec[N]; void Link(int f,int t,int v) { noww[++cnt]=p[f],p[f]=cnt; goal[cnt]=t,val[cnt]=v; noww[++cnt]=p[t],p[t]=cnt; goal[cnt]=f,val[cnt]=v; } void Getcore(int nde,int fth) { siz[nde]=1; int sizz=0; for(int i=p[nde];i;i=noww[i]) if(goal[i]!=fth&&!vis[goal[i]]) { Getcore(goal[i],nde); siz[nde]+=siz[goal[i]]; sizz=max(sizz,siz[goal[i]]); } sizz=max(sizz,tot-siz[nde]); if(sizz<maxs) maxs=sizz,cor=nde; } void DFS(int nde,int fth) { vec[++pt]=dis[nde]; for(int i=p[nde];i;i=noww[i]) if(goal[i]!=fth&&!vis[goal[i]]) dis[goal[i]]=dis[nde]+val[i],DFS(goal[i],nde); } int Calc(int nde,int fir) { int ret=0; pt=0,dis[nde]=fir,DFS(nde,0); sort(vec+1,vec+1+pt); int lpt=1,rpt=pt; while(lpt<rpt) { while(lpt<rpt&&vec[lpt]+vec[rpt]>k) rpt--; ret+=rpt-lpt,lpt++; } return ret; } void PDC(int nde) { maxs=n,cor=nde,Getcore(nde,0); ans+=Calc(cor,0),vis[cor]=true; for(int i=p[cor];i;i=noww[i]) if(!vis[goal[i]]) ans-=Calc(goal[i],val[i]); for(int i=p[cor];i;i=noww[i]) if(!vis[goal[i]]) tot=siz[goal[i]],PDC(goal[i]); } int main() { scanf("%d",&n); for(int i=1;i<n;i++) scanf("%d%d%d",&t1,&t2,&t3),Link(t1,t2,t3); scanf("%d",&k),tot=n,PDC(1); printf("%d",ans); return 0; }
第二个种是逐个加入子树
举个例子
洛谷点分治模板 P3806
数次询问树上是否存在长度为$k$的路径
每次都用上面的做法做两次再一减!复杂度$O(nmlog^2 n)$ -> gg
可过的做法是开个桶,每次逐个加入子树里点到重心的距离同时用桶判断是否有对应的(注意当然是先判再加啦)
另外开桶这个操作很常用的说,但是注意零的问题(一般来说应该是让零一直在桶里,表示从重心戳下去的一条路径;但是如果有零边清桶的时候可能顺带就被清掉了,算了我感觉只有我会犯这种沙雕错误)
复杂度$O(nmlog n)$
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=20005,M=1e7+70,K=110; 6 int n,m,k,t1,t2,t3,pt,cnt,cor,pts,tot,maxx,maxs; 7 int p[N],noww[N],goal[N],val[N]; 8 int vis[N],siz[N],dis[N],vec[N]; 9 int qry[K],ans[K],pos[N],bkt[M]; 10 void Maxi(int &x,int y) 11 { 12 if(x<y) x=y; 13 } 14 void Link(int f,int t,int v) 15 { 16 noww[++cnt]=p[f],p[f]=cnt; 17 goal[cnt]=t,val[cnt]=v; 18 noww[++cnt]=p[t],p[t]=cnt; 19 goal[cnt]=f,val[cnt]=v; 20 } 21 void Getcore(int nde,int fth) 22 { 23 siz[nde]=1; int sizz=0; 24 for(int i=p[nde];i;i=noww[i]) 25 if(goal[i]!=fth&&!vis[goal[i]]) 26 { 27 Getcore(goal[i],nde); 28 siz[nde]+=siz[goal[i]]; 29 if(siz[goal[i]]>sizz) 30 sizz=siz[goal[i]]; 31 } 32 if(tot-siz[nde]>sizz) 33 sizz=tot-siz[nde]; 34 if(sizz<maxs) 35 maxs=sizz,cor=nde; 36 } 37 void DFS(int nde,int fth) 38 { 39 vec[++pt]=dis[nde]; 40 for(int i=p[nde];i;i=noww[i]) 41 if(goal[i]!=fth&&!vis[goal[i]]) 42 dis[goal[i]]=dis[nde]+val[i],DFS(goal[i],nde); 43 } 44 /*int Calc(int nde,int fir) 45 { 46 int ret=0; 47 dis[nde]=fir,pt=0; 48 ll=1e9,rr=0,DFS(nde,0); 49 while(ll<=rr) 50 { 51 while(bkt[ll]) 52 bkt[vec[++pt]=ll]--; 53 ll++; 54 } 55 int lpt=1,rpt=pt; 56 while(lpt<rpt) 57 { 58 while(lpt<rpt&&vec[lpt]+vec[rpt]>k) rpt--; 59 ret+=rpt-lpt,lpt++; 60 } 61 return ret; 62 }*/ 63 void PDC(int nde) 64 { 65 maxs=n,cor=nde,Getcore(nde,0); 66 vis[cor]=true,pts=0; //printf("%d ",cor); 67 for(int i=p[cor];i;i=noww[i]) 68 if(!vis[goal[i]]) 69 { 70 dis[goal[i]]=val[i]; 71 pt=0,DFS(goal[i],nde); 72 for(int j=1;j<=pt;j++) 73 for(int k=1;k<=m;k++) 74 if(vec[j]<=qry[k]) 75 ans[k]|=bkt[qry[k]-vec[j]]; 76 for(int j=1;j<=pt;j++) 77 if(vec[j]<=maxx&&vec[j]) 78 bkt[vec[j]]++,pos[++pts]=vec[j]; 79 } 80 for(int i=1;i<=pts;i++) bkt[pos[i]]=0; 81 for(int i=p[cor];i;i=noww[i]) 82 if(!vis[goal[i]]) tot=siz[goal[i]],PDC(goal[i]); 83 } 84 int main() 85 { 86 scanf("%d%d",&n,&m),bkt[0]=1; 87 for(int i=1;i<n;i++) 88 scanf("%d%d%d",&t1,&t2,&t3),Link(t1,t2,t3); 89 for(int i=1;i<=m;i++) 90 scanf("%d",&qry[i]),Maxi(maxx,qry[i]); 91 tot=n,PDC(1); 92 for(int i=1;i<=m;i++) ans[i]?puts("AYE"):puts("NAY"); 93 return 0; 94 }
最后我们再想一想:如果第二种方法用在第一题上,我们就需要一个数(shu)据(zhuang)结(shu)构(zu)来查询前缀和,稍微麻烦一点;如果第一种方法用在第二题上,呃,你可能只能强行容斥然后被卡=。=感觉总体来说第二种方法更泛用一些
胡乱总结一下
第一种适用于批量统计/统计对象较大(同时能容斥,当然的)的情况,第二种适用于精确统计/统计对象较小的情况
最后,点分治一定要动手写!嘴巴的话谁都会那几句话,没用的=。=
其他的一些点分治题目
把洛谷板子题随便改改就行了
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=400005,M=1e6+60,inf=0x3f3f3f3f; 6 int n,k,t1,t2,t3,pt,cnt,cor,pts,tot,ans,maxx,maxs; 7 int vis[N],siz[N],dis[N],pos[N],bkt[M]; 8 int p[N],noww[N],goal[N],val[N]; 9 pair<int,int> vec[N]; 10 void Maxi(int &x,int y){if(x<y) x=y;} 11 void Mini(int &x,int y){if(x>y) x=y;} 12 void Link(int f,int t,int v) 13 { 14 noww[++cnt]=p[f],p[f]=cnt; 15 goal[cnt]=t,val[cnt]=v; 16 noww[++cnt]=p[t],p[t]=cnt; 17 goal[cnt]=f,val[cnt]=v; 18 } 19 void Getcore(int nde,int fth) 20 { 21 siz[nde]=1; int sizz=0; 22 for(int i=p[nde];i;i=noww[i]) 23 if(goal[i]!=fth&&!vis[goal[i]]) 24 { 25 Getcore(goal[i],nde); 26 siz[nde]+=siz[goal[i]]; 27 if(siz[goal[i]]>sizz) 28 sizz=siz[goal[i]]; 29 } 30 if(tot-siz[nde]>sizz) 31 sizz=tot-siz[nde]; 32 if(sizz<maxs) 33 maxs=sizz,cor=nde; 34 } 35 void DFS(int nde,int fth,int lth) 36 { 37 vec[++pt]=make_pair(dis[nde],lth); 38 for(int i=p[nde];i;i=noww[i]) 39 if(goal[i]!=fth&&!vis[goal[i]]) 40 dis[goal[i]]=dis[nde]+val[i],DFS(goal[i],nde,lth+1); 41 } 42 void PDC(int nde) 43 { 44 maxs=n,cor=nde,Getcore(nde,0); 45 vis[cor]=true,pts=0; 46 for(int i=p[cor];i;i=noww[i]) 47 if(!vis[goal[i]]) 48 { 49 dis[goal[i]]=val[i]; 50 pt=0,DFS(goal[i],cor,1); 51 for(int j=1,t;j<=pt;j++) 52 if((t=vec[j].first)<=k) 53 ans=min(ans,bkt[k-t]+vec[j].second); 54 for(int j=1;j<=pt;j++) 55 { 56 int len=vec[j].first; 57 if(len<M) Mini(bkt[len],vec[j].second),pos[++pts]=len; 58 } 59 } 60 for(int i=1;i<=pts;i++) bkt[pos[i]]=inf; 61 for(int i=p[cor];i;i=noww[i]) 62 if(!vis[goal[i]]) tot=siz[goal[i]],PDC(goal[i]); 63 } 64 int main() 65 { 66 scanf("%d%d",&n,&k),ans=inf; 67 memset(bkt,inf,sizeof bkt),bkt[0]=0; 68 for(int i=1;i<n;i++) 69 { 70 scanf("%d%d%d",&t1,&t2,&t3); 71 Link(t1+1,t2+1,t3),Maxi(maxx,t3); 72 } 73 Mini(maxx,k),tot=n,PDC(1); 74 ans==inf?printf("-1"):printf("%d",ans); 75 return 0; 76 }
同样也是逐个加入子树的做法
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=40005; 6 int n,k,t1,t2,t3,pt; 7 int cnt,cor,pts,tot,ans,maxs; 8 int p[N],noww[N],goal[N],val[N]; 9 int vis[N],siz[N],dis[N],bkt[4],tmp[4]; 10 void Link(int f,int t,int v) 11 { 12 noww[++cnt]=p[f],p[f]=cnt; 13 goal[cnt]=t,val[cnt]=v; 14 noww[++cnt]=p[t],p[t]=cnt; 15 goal[cnt]=f,val[cnt]=v; 16 } 17 void Getcore(int nde,int fth) 18 { 19 siz[nde]=1; int sizz=0; 20 for(int i=p[nde];i;i=noww[i]) 21 if(goal[i]!=fth&&!vis[goal[i]]) 22 { 23 Getcore(goal[i],nde); 24 siz[nde]+=siz[goal[i]]; 25 if(siz[goal[i]]>sizz) 26 sizz=siz[goal[i]]; 27 } 28 if(tot-siz[nde]>sizz) 29 sizz=tot-siz[nde]; 30 if(sizz<maxs) 31 maxs=sizz,cor=nde; 32 } 33 void DFS(int nde,int fth) 34 { 35 tmp[dis[nde]]++; 36 for(int i=p[nde];i;i=noww[i]) 37 if(goal[i]!=fth&&!vis[goal[i]]) 38 dis[goal[i]]=(dis[nde]+val[i])%3,DFS(goal[i],nde); 39 } 40 void PDC(int nde) 41 { 42 maxs=n,cor=nde,Getcore(nde,0); 43 vis[cor]=true,bkt[0]=1,bkt[1]=bkt[2]=0; 44 for(int i=p[cor];i;i=noww[i]) 45 if(!vis[goal[i]]) 46 { 47 dis[goal[i]]=val[i]; 48 tmp[0]=tmp[1]=tmp[2]=0,DFS(goal[i],cor); 49 ans+=tmp[0]*bkt[0]+tmp[1]*bkt[2]+tmp[2]*bkt[1]; 50 bkt[0]+=tmp[0],bkt[1]+=tmp[1],bkt[2]+=tmp[2]; 51 } 52 bkt[0]=bkt[1]=bkt[2]=0; 53 for(int i=p[cor];i;i=noww[i]) 54 if(!vis[goal[i]]) tot=siz[goal[i]],PDC(goal[i]); 55 } 56 int main() 57 { 58 scanf("%d",&n); 59 for(int i=1;i<n;i++) 60 scanf("%d%d%d",&t1,&t2,&t3),Link(t1,t2,t3%3); 61 tot=n,PDC(1); 62 int g=__gcd(ans*2+n,n*n); 63 printf("%d/%d",(ans*2+n)/g,n*n/g); 64 return 0; 65 }
点分治实现树上斜率优化,有题解了链过去了
动态点分治
保留下点分治递归重心的结构,得到一棵树,称它为点分树。因为点分治的性质保证了点分树的树高不超过log,所以可以通过暴力爬树的方法在上面维护一些信息。一般来说是记录点分树子树信息和到点分树父亲的信息,统计的时候要容斥一下不然会算重
注意:点分树的结构和原树的结构没有太大的关系,引用tbl的一句话:
“需要注意的是点分树唯二的性质是点分树上两点的lca一定在原树两点的路径上以及点分树的一个子树总是原树上的一个联通块,除此之外点分树和原树的联系相当的微小,基本可以看做没有联系”
挂链接.jpg
卡不动了,洛谷80pts告辞
(点分树)每个点开两个可删堆,分别维护子树中和到父亲的最大值
01分数规划,先二分之后变成找长度大于0的边数在$[L,R]$之间的路径
然后开始拼路径,发现随着深度增大对应的边数的区间不断左移,所以用单调队列总复杂度就可以做到$O(nlog^2 n)$了。
(好像合并和深度有关的信息需要按最大深度从小到大合并?
1 // luogu-judger-enable-o2 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=200005; 7 const double inf=1e9,eps=1e-8; 8 int n,dw,up,t1,t2,pt,cnt,tot,cor,pts,lst,able,maxs; 9 int p[N],noww[N],goal[N],siz[N],vis[N],cre[N]; 10 int len[N],que[N],pas[N],quq[N]; 11 double t3,dis[N],dp[N],val[N]; 12 template<class Type> void Maxi(Type &x,Type y){if(x<y) x=y;} 13 void Link(int f,int t,double v) 14 { 15 noww[++cnt]=p[f],p[f]=cnt; 16 goal[cnt]=t,val[cnt]=v; 17 noww[++cnt]=p[t],p[t]=cnt; 18 goal[cnt]=f,val[cnt]=v; 19 } 20 void Getcore(int nde,int fth) 21 { 22 siz[nde]=1; int sizz=0; 23 for(int i=p[nde];i;i=noww[i]) 24 if(goal[i]!=fth&&!vis[goal[i]]) 25 { 26 Getcore(goal[i],nde); 27 siz[nde]+=siz[goal[i]]; 28 Maxi(sizz,siz[goal[i]]); 29 } 30 Maxi(sizz,tot-siz[nde]); 31 if(sizz<maxs) maxs=sizz,cor=nde; 32 } 33 void Pre(int nde) 34 { 35 maxs=n,cor=nde,Getcore(nde,0); 36 cre[++pt]=cor,vis[cor]=true; 37 for(int i=p[cor],g;i;i=noww[i]) 38 if(!vis[g=goal[i]]) tot=siz[g],Pre(g); 39 } 40 41 void BFS(int nde) 42 { 43 que[++pts]=nde,pas[nde]=true; 44 for(int i=lst+1;i<=pts;i++) 45 { 46 int tn=que[i]; 47 for(int j=p[tn],g;j;j=noww[j]) 48 if(!pas[g=goal[j]]&&!vis[g]) 49 { 50 dis[g]=dis[tn]+val[j],len[g]=len[tn]+1; 51 que[++pts]=g,pas[g]=true; 52 } 53 } 54 for(int i=lst+1;i<=pts;i++) pas[que[i]]=false; 55 } 56 void Scan() 57 { 58 int f=1,b=0,po=lst+1; 59 for(int i=min(len[que[pts]],up);~i;i--) 60 { 61 int ll=max(0,dw-i),rr=up-i; 62 while(f<=b&&len[quq[f]]<ll) f++; 63 while(po<=pts&&len[que[po]]<ll) po++; 64 while(po<=pts&&len[que[po]]<=rr) 65 { 66 while(f<=b&&dis[quq[b]]-dis[que[po]]<=eps) b--; 67 quq[++b]=que[po++]; 68 } 69 if(f<=b&&dp[i]+dis[quq[f]]>=eps) {able=true; return;} 70 } 71 } 72 void Solve() 73 { 74 int c=cre[++pt]; 75 vis[c]=true,que[pts=0]=c; 76 dp[0]=dis[c]=len[c]=0; 77 for(int i=p[c],g;i;i=noww[i]) 78 if(!vis[g=goal[i]]) 79 { 80 dis[g]=val[i],len[g]=1; 81 lst=pts,BFS(g),Scan(); 82 for(int j=lst+1,k;j<=pts;j++) 83 k=que[j],Maxi(dp[len[k]],dis[k]); 84 } 85 for(int i=0;i<=pts;i++) 86 dp[len[que[i]]]=-inf; 87 for(int i=p[c];i;i=noww[i]) 88 if(!vis[goal[i]]) Solve(); 89 } 90 bool Check(double x) 91 { 92 for(int i=1;i<=cnt;i++) val[i]-=x; 93 memset(vis,able=0,sizeof vis),pt=0,Solve(); 94 for(int i=1;i<=cnt;i++) val[i]+=x; 95 return able; 96 } 97 int main() 98 { 99 scanf("%d%d%d",&n,&dw,&up); 100 for(int i=1;i<n;i++) 101 scanf("%d%d%lf",&t1,&t2,&t3),Link(t1,t2,t3); 102 for(int i=1;i<=n;i++) dp[i]=-inf; 103 tot=n,Pre(1); double l=1,r=1e6; 104 for(int i=1;i<=36;i++) 105 { 106 double mid=(l+r)/2; 107 if(Check(mid)) l=mid; 108 else r=mid; 109 } 110 printf("%.3f",l); 111 return 0; 112 }