点分治,是处理树上路径的一个极好的工具
重心:找到一个点,其所有的子树中最大的子树的节点数最少,那么这个点就是这棵树的重心,
1存储结构:伪链表。
struct node
{
int to,next,len;
}t[2*maxn+5];
void addedge(int u,int v,int l)
{
cnt ++;
t[cnt].to = v;
t[cnt].next = head[u];
t[cnt].len = l;
head[u] = cnt;
return;
}
2递归找重心:
void getroot(int u,int fa) //重心节点保存在全局变量root中。
{
sim[u] = 1; mxson[u] = 0;
for(int i = head[u];i;i = t[i].next)
{
int v = t[i].to;
if(v == fa || vis[v])continue;
getroot(v,u);
sim[u] = sim[u] + sim[v];
mxson[u] = max(sim[v],mxson[u]);
}
mxson[u] = max(mxson[u],S-sim[u]);
if(mxson[u]<MX){root = u;MX = mxson[u];}
}
3找所有点到根节点的距离
void getdis(int u,int fa,int dist) //距离保=保存在全局变量dis[]中。
{
dis[++summar] = dist;
for(int i = head[u];i;i=t[i].next)
{
int v = t[i].to;
if(vis[v]||v == fa)continue;
getdis(v,u,dist+t[i].len);
}
return;
}
4计算结果(排除多余的重复结果,容斥原理)
void Divide(int tr) //tr是根节点,ans是答案,consolate()计算--,依据情况而定,
{
ans = ans + consolate(tr,0);
vis[tr] = true;
for(int i = head[tr];i;i = t[i].next)
{
int v = t[i].to;
if(vis[v])continue;
ans = ans - consolate(v,t[i].len);
S = sim[v]; root = 0;
MX = INF; getroot(v,0);
Divide(root);
}
return;
}
例题1】【poj1741】tree 边权值小于等于K的路径数
给一颗n个节点的树,每条边上有一个距离v。定义d(u,v)为u到v的最小距离。给定k值,求有多少点对(u,v)使u到v的距离小于等于k。
分析
在consolate函数,计算点到根的距离,然后通过根节点就可以把所有的点连接起来。
上面图A为根节点,C-A距离加上A-E距离就可以得出C-E的距离,C-A距离加上A-D的距离就可以得出C-D距离,显然这种情况会出现错误,但是在容斥里面会排除,不用管,
那么将每一个节点到根的距离排序,从距离最小的节点枚举(这个点为起点),
while(L<=R)
{
if(dis[R] + dis[L]<=k){tep = tep + R - L; L++;}
else R--;
}
//L 为起始点,如果加上距离最大R的值小于K,那么L-R的点都满足距离小于K,那么就有R-L条路经满足。
完整代码:
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 10002
#define INF 2147483646
#define ll long long
using namespace std;
inline int gi()
{
int date = 0,m = 1; char ch = 0;
while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
if(ch=='-'){m = -1; ch = getchar();}
while(ch>='0' && ch<='9')
{
date = date*10+ch-'0';
ch = getchar();
}return date*m;
}
inline void write(ll qw)
{
if(qw<0){putchar('-'); qw = -qw;}
if(qw>9)write(qw/10);
putchar(qw%10+'0');
}
//权值小于等下K
struct node
{
int to,next,len;
}t[2*maxn+5];
int head[maxn+5];
//head[]--t构成伪链表来存储树;
int sim[maxn+5],mxson[maxn+5]; // sim[i]---表示i为节点的子树的个数,mxson[i]---表示i为节点的根中最大子树。
bool vis[maxn+5]; //vis【i】--在容斥中用到,判断i之前是否用过。
int MX,root,dis[maxn+5],summar; //dis[i]---表示节点i到根节点的距离。
ll ans;
int n,k,cnt,S;
void addedge(int u,int v,int l) //u,v是顶点,l是权值。
{
cnt ++;
t[cnt].to = v;
t[cnt].next = head[u];
t[cnt].len = l;
head[u] = cnt;
return;
}
void getroot(int u,int fa)
{
sim[u] = 1; mxson[u] = 0;
for(int i = head[u];i;i = t[i].next)
{
int v = t[i].to;
if(v == fa || vis[v])continue;
getroot(v,u);
sim[u] = sim[u] + sim[v];
mxson[u] = max(sim[v],mxson[u]);
}
mxson[u] = max(mxson[u],S-sim[u]);
if(mxson[u]<MX){root = u;MX = mxson[u];}
}
void getdis(int u,int fa,int dist) //得到所有点到根的距离,u是根,fa是父节点, dist是距离,
{
dis[++summar] = dist;
for(int i = head[u];i;i=t[i].next)
{
int v = t[i].to;
if(vis[v]||v == fa)continue;
getdis(v,u,dist+t[i].len);
}
return;
}
int consolate(int sta,int len1)
{
summar = 0;
memset(dis,0,sizeof(dis));
getdis(sta,0,len1);
sort(dis+1,dis+summar+1);
int L = 1,R = summar,tep=0;
while(L<=R)
{
if(dis[R] + dis[L]<=k){tep = tep + R - L; L++;}
else R--;
}
return tep;
}
void Divide(int tr)
{
ans = ans + consolate(tr,0);
vis[tr] = true;
for(int i = head[tr];i;i = t[i].next)
{
int v = t[i].to;
if(vis[v])continue;
ans = ans - consolate(v,t[i].len);
S = sim[v]; root = 0;
MX = INF; getroot(v,0);
Divide(root);
}
return;
}
int main()
{
freopen("Tree.in","r",stdin);
int u,v,l;
while(1)
{
n = gi(); k = gi();
if(n == 0 && k == 0)break;
cnt = 0; memset(head,0,sizeof(head));
for(int i = 1; i <= n - 1; i ++)
{
u = gi(); v = gi(); l = gi();
addedge(u,v,l); addedge(v,u,l);
}
ans = 0;
memset(vis,0,sizeof(vis));
MX = INF ; S = n; getroot(1,0);
Divide(root);
write(ans);putchar('\n');
}
return 0;
}
【例题3】【luogu P3806】点分治1 询问边权值为K的路径是否存在
给定一棵树,询问长度为k的树上路径是否存在。
对于每个k每行输出一个答案,存在输出“AYE”,否则输出”NAY”。(k<=10000000)
分析
注意到k的大小在数组可开范围之内,开一个数组sum[i]记录长度为i的路径条数。
点分治计算所有路径长度时注意容斥原理去重,然后在线直接O(1)回答询问即可。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 100100
#define INF 2147483646
#define ll long long
using namespace std;
inline int gi()
{
int date = 0,m = 1; char ch = 0;
while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
if(ch=='-'){m = -1; ch = getchar();}
while(ch>='0' && ch<='9')
{
date = date*10+ch-'0';
ch = getchar();
}return date*m;
}
//权值等于k
int sum[10000010];
struct node
{
int to,next,len;
}t[maxn+5];
int head[maxn+5],cnt;
int sim[maxn+5],mxson[maxn+5],dis[maxn+5];
int n,m,MX,root,sumary,Smer;
bool vis[maxn+5];
void addedge(int s,int f,int l)
{
cnt ++;
t[cnt].to = f;
t[cnt].next = head[s];
t[cnt].len = l;
head[s] = cnt;
return;
}
void getroot(int u,int fa)
{
sim[u] = 1; mxson[u] = 0;
for(int i = head[u];i;i = t[i].next)
{
int v = t[i].to;
if(vis[v]||v == fa)continue;
getroot(v,u);
sim[u] = sim[u] + sim[v];
mxson[u] = max(mxson[u],sim[v]);
}
mxson[u] = max(mxson[u],Smer - sim[u]);
if(mxson[u]<MX){root = u; MX = mxson[u];}
}
void getdis(int u,int fa,int dist)
{
dis[++sumary] = dist;
for(int i = head[u];i;i = t[i].next)
{
int v = t[i].to;
if(vis[v]||v == fa)continue;
getdis(v,u,lon+t[i].len);
}
return;
}
void consolate(int st,int prim,int jud)
{
sumary = 0;
getdis(st,0,prim);
if(jud == 1)
{
for(int i = 1;i <= sumary-1; i ++)
for(int j = i+1;j <= sumary; j ++)
sum[dis[i]+dis[j]]++;
}
else if(jud == 0)
{
for(int i = 1; i <= sumary-1; i ++)
for(int j = i+1; j <= sumary; j ++)
sum[dis[i]+dis[j]]--;
}return;
}
void divide(int tr)
{
vis[tr] = true;
consolate(tr,0,1);
for(int i = head[tr];i;i = t[i].next)
{
int v = t[i].to;
if(vis[v])continue;
consolate(v,t[i].len,0);
MX = INF; root = 0; Smer = sim[v];
getroot(v,0);
divide(root);
}
return;
}
void give_ans()
{
for(int i = 1; i <= m ; i ++)
{
int k = gi();
if(sum[k])printf("AYE\n");
else printf("NAY\n");
}
return;
}
int main()
{
freopen("divide.in","r",stdin);
n = gi(); m = gi(); cnt = 0;
for(int i = 1; i <= n-1 ; i++)
{
int x = gi(),y = gi(),l = gi();
addedge(x,y,l); addedge(y,x,l);
}
root = 0; MX = INF; Smer = n;
getroot(1,0);
divide(root);
give_ans();
return 0;
}
【luogu P2634】聪聪可可
输入一个树, 问有多少对点之间的距离模3为0, 并按照题目所给方式输出
分析:
利用点分治在计算所有路径长度,把路径长度对3取模,用t[0],t[1],t[2]分别记录模为0、1、2的情况,那么显然答案就是t[1]*t[2]*2+t[0]*t[0]。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 20002
#define INF 2147483646
#define mod 3
#define ll long long
using namespace std;
inline int gi()
{
int date = 0,m = 1; char ch = 0;
while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
if(ch=='-'){m = -1; ch = getchar();}
while(ch>='0' && ch<='9')
{
date = date*10+ch-'0';
ch = getchar();
}return date*m;
}
inline void write(ll qw)
{
if(qw<0){putchar('-'); qw = -qw;}
if(qw>9)write(qw/10);
putchar(qw%10+'0');
}
struct node{int to,next,len;}t[2*maxn+5];
int head[maxn+5],cnt;
int sim[maxn+5],mxson[maxn+5],sum[4];
ll ans;
int Sumer,MX,dec1,root,n;
bool vis[maxn+5];
void addedge(int u,int v,int l)
{
cnt ++;
t[cnt].to = v;
t[cnt].next = head[u];
t[cnt].len = l;
head[u] = cnt;
return;
}
void getroot(int u,int fa)
{
sim[u] = 1;mxson[u] = 0;
for(int i = head[u];i;i = t[i].next)
{
int v= t[i].to;
if(v == fa || vis[v])continue;
getroot(v,u);
sim[u] = sim[u] + sim[v];
mxson[u] = max(mxson[u],sim[v]);
}
mxson[u] = max(mxson[u],Sumer - sim[u]);
if(mxson[u]<MX){MX = mxson[u]; root = u;}
}
void getdis(int u,int fa,int lon)
{
sum[lon%mod]++;
for(int i = head[u];i;i = t[i].next)
{
int v = t[i].to;
if(vis[v]||v == fa)continue;
getdis(v,u,(lon+t[i].len)%mod);
}return;
}
ll consolate(int st,int prim_len)
{
sum[0] = sum[1] = sum[2] = 0;
getdis(st,0,prim_len);
ll tep = 2*sum[1]*sum[2] + sum[0]*(sum[0]-1) + sum[0];
return tep;
}
void Divide(int temproot)
{
ans = ans + consolate(temproot,0);
vis[temproot] = true;
for(int i = head[temproot];i;i = t[i].next)
{
int v = t[i].to;
if(vis[v])continue;
ans = ans - consolate(v,t[i].len);
MX = INF; root = 0; Sumer = sim[v];
getroot(v,0);
Divide(root);
}
return;
}
void gcd(ll a,ll b)
{
if(b == 0)dec1 = a;
else gcd(b,a%b);
}
int main()
{
freopen("game.in","r",stdin);
n = gi();
for(int i = 1; i <= n-1 ; i ++)
{
int u = gi(),v= gi(),l = gi()%mod;
addedge(u,v,l);addedge(v,u,l);
}
MX = INF; root = 0;
Sumer = n; ans = 0;
getroot(1,0);
Divide(root);
ll tep1 = n*n; gcd(ans,tep1);
write(ans/dec1); putchar('/'); write(tep1/dec1);
return 0;
}
Description 固定边权的路径最小数
给一棵树n个点,n-1条边,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000;
分析:
增设一个数组,不光要记录路径的权值和,还要记录路径的长度 ,
需要加一个数组num[i],表示长度为i且权值和为m的路径条数,
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=200010;
int f[N],sz[N],tt;
int n,k,root,sum,tot=0,head[N],num[N];
//num[i]--表示长度为i且权值和为k的路径条数
struct node{
int x,y,v,nxt;
};
node t[N<<1]; //t和head组成了伪链表。
bool vis[N]; //vis【i】--在容斥中用到,判断i之前是否用过。
struct po{
int len,v;
};
po line[N<<1],d[N];//d在一个连通块内在到达根节点的路径权值和以及边数
void add(int u,int w,int z)
{
tot++;
t[tot].x=u;t[tot].y=w;t[tot].v=z;t[tot].nxt=head[u];head[u]=tot;
}
int cmp(const po &a,const po &b)
{
return a.v<b.v||a.v==b.v&&a.len<b.len;
}
void getroot(int now,int fa)
{
f[now]=0;
sz[now]=1;
for (int i=head[now];i;i=t[i].nxt)
if (t[i].y!=fa&&!vis[t[i].y])
{
getroot(t[i].y,now);
sz[now]+=sz[t[i].y];
f[now]=max(f[now],sz[t[i].y]);
}
f[now]=max(f[now],sum-sz[now]);
if (f[now]<f[root]) root=now;
}
void getdeep(int now,int fa)
{
d[++tt]=line[now];
for (int i=head[now];i;i=t[i].nxt)
if (t[i].y!=fa&&!vis[t[i].y])
{
line[t[i].y].v=line[now].v+t[i].v;
line[t[i].y].len=line[now].len+1;
getdeep(t[i].y,now);
}
}
int cal(int now,int z)
{
tt=0;
getdeep(now,0);
sort(d+1,d+1+tt,cmp);
int l=1,r=tt;
while (l<r)
{
while (d[l].v+d[r].v>k&&r>l) r--;
int rr=r;
if (r<=l) break;
while (d[l].v+d[r].v==k)
{
num[d[l].len+d[r].len]+=z;
r--;
if (r<=l) break;
}
r=rr;
l++;
}
}
void divide(int now)
{
line[now].len=0; line[now].v=0;
vis[now]=1;
cal(now,1);
for (int i=head[now];i;i=t[i].nxt)
if (!vis[t[i].y])
{
line[now].len=1; line[now].v=t[i].v;
cal(t[i].y,-1); //一定要有去重操作
root=0;
sum=sz[t[i].y];
getroot(t[i].y,root);
divide(root);
}
}
int main()
{
scanf("%d%d",&n,&k);
for (int i=1;i<n;i++)
{
int u,w,z;
scanf("%d%d%d",&u,&w,&z);
u++; w++;
add(u,w,z); add(w,u,z);
}
f[0]=1000000000; sum=n; root=0;
getroot(1,0);
divide(root);
for (int i=0;i<=n;i++)
if (num[i]>0) {
printf("%d",i);
return 0;
}
printf("-1");
return 0;
}