点分治
解决可带权树上简单路径统计问题
其精髓在于把无根树平均地分割成若干互不影响的子问题求解,极大降低了时间复杂度,是一种巧妙的暴力。
例: 模板给定一棵树(无根树)和一个整数 k ,求树上等于 k 的路径有多少条?
(存在)
枚举不同的两个点,然后dfs算出ta们间的距离,统计一下就行了 大概是 O(n^3) 的复杂度
n一大显然爆炸
那找个根,求出每个点到根的距离,然后枚举两个点,求 lca ,简单加减一下就行了
大概是 O(n^2logn) 的复杂度。。。?
可n大了还会爆炸
原理
假设我们选出一个根 Root ,那么答案路径肯定分两种:
要么被一个子树所包含;要么就是跨过 Root ,在两个子树内分别选择一部分路径,然后从 Root 处拼起来形成一条答案路径
注意到,情况1(被一个子树包含)中,答案路径上的一点 变为根 Root ,就成了情况2(在两棵子树中)——也就是说往下继续搜找到一个根就成了情况2
这就是点分治的基本原理
选根(选重心)
首先根不能随便选,选根不同会影下面遍历的效率的
显然可以发现找树的重心是最优的
定义一棵树的重心为以该点为根时最大子树最小的点。
性质:以重心为根,任意一棵子树的大小都不超过整棵树大小的一半。
由重心的性质可得,总递归次数不超过O(logn)
总复杂度$ O(nlog_2n) $ n为子树节点个数
https://www.cnblogs.com/bztMinamoto/p/9489473.html
https://blog.csdn.net/a_forever_dream/article/details/81778649
模板
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=200500;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
int n,m;
int ques[105],ok[105];
int hd[N],nxt[N],to[N],w[N],tot;
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
bool vis[N];
int siz[N],big,root,S;
inline void find_root(int x,int fa) {
siz[x]=1;
int max_part=0;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==fa || vis[y]) continue;
find_root(y,x);
siz[x]+=siz[y];
Max(max_part,siz[y]);
}
Max(max_part,S-siz[x]);
if(max_part<big) big=max_part,root=x;
}
int d[N],cnt;
inline void get_dis(int x,int fa,int dis) {
d[++cnt]=dis;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==fa || vis[y]) continue;
get_dis(y,x,dis+w[i]);
}
}
const int LIMIT=1e7;
int mp[10000005],t;//计数器
int era[N];
inline void solve(int x) {
vis[x]=1;
mp[t=0]=1;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(vis[y]) continue;
cnt=0;
get_dis(y,x,w[i]);
for(int j=1;j<=cnt;j++) {
if(d[j]>LIMIT) continue;
era[++t]=d[j];
for(int k=1;k<=m;k++)
if(d[j]<=ques[k]&&mp[ques[k]-d[j]])
ok[k]=1;
}
for(int j=1;j<=cnt;j++)
if(d[j]<=LIMIT) mp[d[j]]=1;
}
for(int i=1;i<=t;i++)
mp[era[i]]=0;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(vis[y]) continue;
big=S=siz[y];
find_root(y,0);
solve(root);
}
}
int main() {
n=read();m=read();
for(int i=1,x,y,z;i<n;i++)
x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
for(int i=1;i<=m;i++)
ques[i]=read();
big=S=root=n+1;find_root(1,0);
solve(root);
for(int i=1;i<=m;i++)
puts(ok[i]?"AYE":"NAY");
return 0;
}
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define inf 999999999
int n,m,e_cnt=0,Size;
struct node{int w,to,nxt;};
node e[20010];
int hd[10010];
int root,ms,size[10010],mson[10010],sum[10000010];
bool v[10010];
void add(int x,int y,int z){
e_cnt++;
e[e_cnt].to=y;
e[e_cnt].w=z;
e[e_cnt].nxt=hd[x];
hd[x]=e_cnt;
}
void getroot(int x,int fa){
size[x]=1;mson[x]=0;
for(int i=hd[x];i;i=e[i].nxt){
int y=e[i].to;
if(v[y]||y==fa)continue;
getroot(y,x);
size[x]+=size[y];
if(size[y]>mson[x])mson[x]=size[y];
}
if(Size-size[x]>mson[x])mson[x]=Size-size[x];
//Size表示当前这整棵树的大小,那么Size-size[x]就表示不在x的子树内的节点数量
if(ms>mson[x])ms=mson[x],root=x;
}
int t;
int dis[10010];
int ask[110],ans[110];
void getdis(int x,int fa,int z){
dis[++t]=z;
for(int i=hd[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==fa||v[y])continue;
getdis(y,x,z+e[i].w);
}
}
struct asd{int x,y;}arr[10010];
int tt;
bool cmp(int x,int y){return x<y;}
void solve(int x,int y,int id){//id容斥
t=0;
getdis(x,0,y);
tt=0;
sort(dis+1,dis+t+1,cmp);
dis[0]=-233;
for(int i=1;i<=t;i++)
if(dis[i]!=dis[i-1]) arr[++tt].x=dis[i],arr[tt].y=1;
else arr[tt].y++;
for(int i=1;i<=m;i++){
if(ask[i]%2==0){
for(int j=1;j<=tt;j++){
if(arr[j].x==ask[i]/2)
ans[i]+=(arr[j].y-1)*arr[j].y/2*id;
}
}
for(int j=1;j<=tt&&arr[j].x<ask[i]/2;j++){
int l=j+1,r=tt;
while(l<=r){
int mid=l+r>>1;
if(arr[j].x+arr[mid].x==ask[i]){
ans[i]+=arr[j].y*arr[mid].y*id;
break;
}
if(arr[j].x+arr[mid].x>ask[i])r=mid-1;
else l=mid+1;
}
}
}
}
void fenzhi(int x,int ssize){
v[x]=true;
solve(x,0,1);
for(int i=hd[x];i;i=e[i].nxt){
int y=e[i].to;
if(v[y])continue;
solve(y,e[i].w,-1);
ms=inf;root=0;
Size=size[y]<size[x]?size[y]:(ssize-size[x]);
getroot(y,0);
fenzhi(root,Size);
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
for(int i=1;i<=m;i++)
scanf("%d",&ask[i]);
root=0;ms=inf;Size=n;
getroot(1,0);
fenzhi(root,n);
for(int i=1;i<=m;i++)
if(ans[i]>0)printf("AYE\n");
else printf("NAY\n");
return 0;
}
TREE
求出树上两点距离小于等于 k 的点对数量
双指针扫描,和刚才的基本类似,把d,mp数组排序,然后双指针扫
快的起飞
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N=2e5+10;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,m,k,ans;
bool vis[N];
int S,big,root;
int siz[N];
int hd[N],nxt[N],to[N],w[N],tot;
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
inline void Max(int &x,int y) {if(x<y) x=y;}
inline void find_root(int x,int fa) {
siz[x]=1;
int max_part=0;
for(int i=hd[x],y;i;i=nxt[i]) {
if(vis[y=to[i]] || y==fa) continue;
find_root(y,x);
siz[x]+=siz[y];
Max(max_part,siz[y]);
}
Max(max_part,S-siz[x]);
if(max_part<big) big=max_part,root=x;
}
vector<int>d,mp;
inline void get_dis(int x,int fa,int dis) {
d.push_back(dis);
for(int i=hd[x],y;i;i=nxt[i]) {
if(vis[y=to[i]] || y==fa) continue;
get_dis(y,x,dis+w[i]);
}
}
int LIMIT=1e7;
inline void solve(int x) {
vis[x]=1;
mp.clear();
mp.push_back(0);
for(int i=hd[x],y;i;i=nxt[i]) {
if(vis[y=to[i]]) continue;
d.clear();
get_dis(y,x,w[i]);
sort(d.begin(),d.end());
sort(mp.begin(),mp.end());
int l=0,r=mp.size()-1;
while(l<d.size()&&r>=0) {
if(d[l]+mp[r]<=k)
ans+=r+1,l++;
else r--;
}
for(int j=0;j<d.size();j++)
mp.push_back(d[j]);
}
for(int i=hd[x],y;i;i=nxt[i]) {
if(vis[y=to[i]]) continue;
S=big=siz[y];
find_root(y,0);
solve(root);
}
}
int main() {
n=read();
for(int i=1,x,y,z;i<n;i++) {
x=read(),y=read(),z=read();
add(x,y,z);add(y,x,z);
}
k=read();
S=big=n+1;
find_root(1,0);
solve(root);
printf("%d\n",ans);
return 0;
}
聪聪可可
这题裸的点分治,略微难的是计数
还是上面的思想,d[]记录这棵子树的信息(这里就不是dis了,而是dis%3的桶)
然后 mp搞成了 t[ ] ——就是桶的意思,记录过去的桶
计数——
t[0] * d[0] *2 ——以前dis是3的倍数的个数,与now是3的倍数的个数一拼还是3的倍数(乘2因为聪聪可以两边选)
t[1] * d[2] * 2 ——以前dis是1的倍数的个数,与now是2的倍数的个数一拼是3的倍数
t[2] * d[1] * 2 ——以前dis是2的倍数的个数,与now是1的倍数的个数一拼是3的倍数
d[0] * 2 自己这棵树选两个都是三的倍数
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int N=200500;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
int n,m;
int hd[N],nxt[N],to[N],w[N],tot;
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
bool vis[N];
int siz[N],big,root,S;
inline void find_root(int x,int fa) {
siz[x]=1;
int max_part=0;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==fa || vis[y]) continue;
find_root(y,x);
siz[x]+=siz[y];
Max(max_part,siz[y]);
}
Max(max_part,S-siz[x]);
if(max_part<big) big=max_part,root=x;
}
int t[N],d[N];
int ans;
inline void get_dis(int x,int fa,int dis) {
d[dis%3]++;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==fa || vis[y]) continue;
get_dis(y,x,dis+w[i]);
}
}
inline void solve(int x) {
vis[x]=1;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(vis[y]) continue;
d[0]=d[1]=d[2]=0;
get_dis(y,x,w[i]);
ans+=t[0]*d[0]*2+t[1]*d[2]*2+t[2]*d[1]*2+d[0]*2;
for(int j=0;j<3;j++) t[j]+=d[j];
}
t[0]=t[1]=t[2]=0;
for(int i=hd[x],y;i;i=nxt[i]) {
if(vis[y=to[i]]) continue;
big=S=siz[y];
find_root(y,0);
solve(root);
}
}
inline int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int main() {
n=read();
for(int i=1,x,y,z;i<n;i++)
x=read(),y=read(),z=read()%3,add(x,y,z),add(y,x,z);
big=S=root=n+1;
find_root(1,0);
solve(root);
ans+=n;//两点重合
int g=gcd(ans,n*n);
printf("%d/%d\n",ans/g,n*n/g);
return 0;
}
Race
https://www.luogu.com.cn/problem/P4149
https://www.luogu.com.cn/problem/CF150E