点分治

解决可带权树上简单路径统计问题

其精髓在于把无根树平均地分割成若干互不影响的子问题求解,极大降低了时间复杂度,是一种巧妙的暴力。


例: 模板给定一棵树(无根树)和一个整数 k ,求树上等于 k 的路径有多少条?

​ (存在)

枚举不同的两个点,然后dfs算出ta们间的距离,统计一下就行了 大概是 O(n^3) 的复杂度

n一大显然爆炸

那找个根,求出每个点到根的距离,然后枚举两个点,求 lca ,简单加减一下就行了

大概是 O(n^2logn) 的复杂度。。。?

可n大了还会爆炸


原理

假设我们选出一个根 Root ,那么答案路径肯定分两种:

要么被一个子树所包含;要么就是跨过 Root ,在两个子树内分别选择一部分路径,然后从 Root 处拼起来形成一条答案路径

注意到,情况1(被一个子树包含)中,答案路径上的一点 变为根 Root ,就成了情况2(在两棵子树中)——也就是说往下继续搜找到一个根就成了情况2

img

这就是点分治的基本原理

选根(选重心)

首先根不能随便选,选根不同会影下面遍历的效率的

显然可以发现找树的重心是最优的

定义一棵树的重心为以该点为根时最大子树最小的点。

性质:以重心为根,任意一棵子树的大小都不超过整棵树大小的一半。

证明最优见

由重心的性质可得,总递归次数不超过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

https://loj.ac/problem/6463

https://loj.ac/submission/132290

https://loj.ac/submission/132377

posted @ 2020-08-22 10:18  ke_xin  阅读(48)  评论(0编辑  收藏  举报