zoukankan      html  css  js  c++  java
  • 点分治

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

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


    例: 模板给定一棵树(无根树)和一个整数 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
    ");
            else printf("NAY
    ");
        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
    ",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
    ",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

  • 相关阅读:
    【转帖】流程与IT管理部——IT支撑业务变革的必然趋势
    【转帖】39个让你受益的HTML5教程
    【转帖】2015年2月份最佳的免费 UI 工具包
    【消息】Pivotal Pivots 开源大数据处理的核心组件
    【转帖】创业者,你为什么这么着急?
    教程:SpagoBI开源商业智能之XML Template 图表模板
    教程:Spagobi开源BI系统 Console报表设计教程
    【转帖】Mysql多维数据仓库指南 第一篇 第1章
    Kiss MySQL goodbye for development and say hello to HSQLDB
    梯度消失和梯度爆炸问题详解
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13544592.html
Copyright © 2011-2022 走看看