放下学习的博客:https://www.cnblogs.com/bztMinamoto/p/9489473.html
首先介绍下点分治,例如我们求路径,路径就是点与点间的,然后路径就两种情况经过该点的路径于不经过该点的路径,所以
我们以一个点开始开始dfs,找出所又路径两辆合并什么的,之后这个点就得删掉了,那接下来树就被分割成两个了,也就是我们的分治就是不断将一棵树分成好几颗小的树。
但是如果是一条链呢?那按我们上门说的,直接爆炸,n的平方复杂度,所以接下来我们引入重心,保持复杂度
实际上,一棵树的最大子树最小的点有一个名称,叫做重心。
暴力求重点
void findrt(int u,int fa){
//sz表示子树的大小,son表示点的最大子树的大小
//cmax(a,b)表示如果b>a则a=b
//个人习惯这样写,或者直接写成a=max(a,b)
sz[u]=1,son[u]=0;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(vis[v]||v==fa) continue;
findrt(v,u);
sz[u]+=sz[v];
cmax(son[u],sz[v]);
}
//size表示整棵树的大小
//因为这是一棵无根树,所以包括它的父亲在内的那一坨也应该算作它的子树
cmax(son[u],size-sz[u]);
if(son[u]<mx) mx=son[u],rt=u;
}
合并的时候有个小问题,先给代码
void divide(int u){
ans+=solve(u,0);
vis[u]=1;
int totsz=size;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(vis[v]) continue;
ans-=solve(v,edge[i]);
mx=inf,rt=0;
size=sz[v]>sz[u]?totsz-sz[u]:sz[v];//这里应该这样写才是对的
findrt(v,0);
divide(rt);
}
}
上面其他的应该都好理解,除了这一句 ans-=solve(v,edge[i]);
考虑一下这棵树

考虑一下,从点11出发的路径有以下几条
1−>41−>4
1−>4−>61−>4−>6
1−>21−>2
1−>2−>31−>2−>3
1−>2−>51−>2−>5
然后我们为了求贡献,会将路径两两合并
然而合并1−>2−>31−>2−>3和1−>2−>51−>2−>5这两条路径实际上是不合法的,因为出现了重边
所以要减去22这一棵子树中的所有路径两两合并的贡献
然后回头来看代码 ans+=solve(u,0); ans-=solve(v,edge[i]);
看到没?第二个参数不一样,这样在考虑子树中两两合并时的贡献时就不会把这一条边的贡献给漏掉了
然后只要递归继续找就可以(*^▽^*)。
然后接下来第一道题:
poj1741tree
给一颗n个节点的树,每条边上有一个距离v。定义d(u,v)为u到v的最小距离。给定k值,求有多少点对(u,v)使u到v的距离小于等于k。
点分的板子……好像基本都是板子套进去……就是注意合并的时候二分保证复杂度
#include <cstdio>
#include <algorithm>
#define N 40005
#define M 80005
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<15,stdin),p1==p2)?EOF:*p1++)
char buf[1<<15],*p1=buf,*p2=buf;
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
int read(){
char c=getchar();int x=0,f=1;
while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1; c=getchar();}
while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘; c=getchar();}
return x*f;
}
int ver[M],Next[M],head[N],edge[M];
int n,tot,root;ll k;
void add(int u,int v,int e){
ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e;
ver[++tot]=u,Next[tot]=head[v],head[v]=tot,edge[tot]=e;
}
int sz[N],vis[N],mx,size;
ll d[N],q[N],l,r;
void getroot(int u,int fa){
sz[u]=1;int num=0;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(v==fa||vis[v]) continue;
getroot(v,u);
sz[u]+=sz[v];
cmax(num,sz[v]);
}
cmax(num,size-sz[u]);
if(num<mx) mx=num,root=u;
}
void getdis(int u,int fa){
q[++r]=d[u];
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(v==fa||vis[v]) continue;
d[v]=d[u]+edge[i];
getdis(v,u);
}
}
ll calc(int u,int val){
r=0;
d[u]=val;
getdis(u,0);
ll sum=0;l=1;
sort(q+1,q+r+1);
while(l<r){
if(q[l]+q[r]<=k) sum+=r-l,++l;
else --r;
}
return sum;
}
ll ans=0;
void dfs(int u){
ans+=calc(u,0);
vis[u]=1;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(vis[v]) continue;
ans-=calc(v,edge[i]);
size=sz[v];
mx=inf;
getroot(v,0);
dfs(root);
}
}
int main(){
//freopen("testdata.in","r",stdin);
while(1){
n=read();k=read();
if(n==0&&k==0) break;
for(int i=1;i<n;++i){
int u=read(),v=read(),e=read();
add(u,v,e);
}
size=n;
mx=inf;
getroot(1,0);
dfs(root);
printf("%lld\n",ans);
ans=tot=0;
for(int i=1;i<=n;i++)
head[i]=vis[i]=sz[i]=q[i]=d[i]=0;
}
return 0;
}
第二:luogu 3806
这道题如果直接按上面做法然后双for来更新答案(例如博客那样)
肯定是不行的n平方不是有病嘛
所以我们换一下,依然二分更新答案
calc函数和get_dis函数不一样,其他都差不多
aa数组记录从root能到的点
dd数组记录a_{i}ai?到root的距离
bb数组记录a_{i}ai?属于root的哪一棵子树(即当b[a[i]]==b[a[j]]时,说明a[i]与a[j]属于root的同一棵子树)
cmp函数:
bool cmp(int x,int y){
return d[x]<d[y];
}
#include<bits/stdc++.h>
using namespace std;
const int M=10001;
const int inf=0x3f3f3f3f;
struct node{int nxt,edg,to;}h[M*2];
int cnt,n,m,query[M],vis[M],b[M],q[M],d[M],ok[M],maxx,sze,sz[M],tot,head[M],root;
bool cmp(int x,int y)
{
return d[x]<d[y];
}
void add(int u,int v,int e)
{
h[++tot].to=v,h[tot].nxt=head[u],head[u]=tot;h[tot].edg=e;
}
void getroot(int u,int fa)
{
sz[u]=1;
int num=0;
for(int i=head[u];i;i=h[i].nxt){
int v=h[i].to;
if(v==fa||vis[v]) continue;
getroot(v,u);
sz[u]+=sz[v];
num=max(num,sz[v]);
}
num=max(num,sze-sz[u]);
if(num<maxx) maxx=num,root=u;
// printf("root=%d\n",root);
}
void getdis(int u,int fa,int dis,int from)
{
q[++cnt]=u;
d[u]=dis;
b[u]=from;
for(int i=head[u];i;i=h[i].nxt)
{
int v=h[i].to;
if(v==fa||vis[v]) continue;
getdis(v,u,h[i].edg+dis,from);
}
}
void cal(int u)
{
cnt=0;
q[++cnt]=u,d[u]=0,b[u]=u;
for(int i=head[u];i;i=h[i].nxt){
int v=h[i].to;
if(vis[v])continue;
getdis(v,u,h[i].edg,v);
}
sort(q+1,q+cnt+1,cmp);
// for(int i=1;i<=cnt+1;i++) printf("q=%d ",q[i]);puts("");
for(int i=1;i<=m;i++){
int l=1,r=cnt;
if(ok[i]) continue;
while(l<r){
if(d[q[l]]+d[q[r]]>query[i]) r--;//长度大于k,r--
else if(d[q[l]]+d[q[r]]<query[i]) l++;//长度小于k,l++
else if(b[q[l]]==b[q[r]]){if(d[q[r]]==d[q[r-1]])r--;else l++;}//长度等于k了但是很遗憾在同一颗紫薯里,也就是他们有公共边,那抱歉如果右边-1的那位可以继续保持,那就r--让他来,因为现在已经是正确答案了,如果不是那只能l++来继续了
else{ok[i]=1;break;}///通过前面三重关卡恭喜你得到了这条路径
}
}
}
void dfs(int u)
{
vis[u]=1;
cal(u);
for(int i=head[u];i;i=h[i].nxt)
{
int v=h[i].to;
if(vis[v]) continue;
sze=sz[v];
maxx=inf;
getroot(v,0);
dfs(root);
}
}
int main()
{
int u,v,e;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&e);
add(u,v,e);add(v,u,e);
}
for(int i=1;i<=m;i++) scanf("%d",&query[i]);
sze=n;
maxx=inf;
getroot(1,0);
dfs(root);
for(int i=1;i<=m;i++)
{
if(ok[i]) cout<<"AYE"<<endl;
else cout<<"NAY"<<endl;
}
}
原文:https://www.cnblogs.com/hgangang/p/11741466.html