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

    来看一道题

    给定一棵有n个点的树
    询问树上距离为k的点对是否存在。
    对于30%的数据n<=100
    对于60%的数据n<=1000,m<=50
    对于100%的数据n<=10000,m<=100,c<=1000,K<=10000000

    首先来看一下30%的点(不要说太简单了,有时候暴力很有用)
    但事实上好水啊
    不是dfs的题吗?这是在你不会任何数据结构的情况下你能做的点,只需要枚举所有点对,在算出他们之间的距离,用一个标记数组将这个距离赋为1,询问的时候就直接判断距离是否为1就可以了。这样就好了,对于蒟蒻来说,会这个就可以了,但是如果你不满足这点分,继续往下看,这里就不提供代码了。

    对于60%的点。
    这个只要你会一点数据结构(lca)就够就可以做了,如果你不会,戳这 如果你会,这就简单了,首先预处理出每一个点到根节点的距离dis。在枚举所有点对,他们之间的距离就是dis[a]+dis[b]-2*dis[lca(a,b)];用一个标记数组将这个距离赋为1,询问的时候就直接判断距离是否为1。开氧气(O2)可以获得70分。

    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    using namespace std;
    int cnt=0,fa[500000],siz[500000],son[500000],dep[500000],top[500000],dfs[500000],head[500000],dis[500001];
    struct node {
        int to,next,v;
    } a[1000001];
    void add(int x,int y,int c) {
        a[++cnt].to=y;
        a[cnt].next=head[x];
        a[cnt].v=c;
        head[x]=cnt;
    }
    void dfs1(int u,int f,int depth) {
        fa[u]=f;
        siz[u]=1;
        dep[u]=depth;
        for(int i=head[u]; i; i=a[i].next) {
            int v=a[i].to;
            if(v==f)
                continue;
            dis[v]=dis[u]+a[i].v;
            dfs1(v,u,depth+1);
            siz[u]+=siz[v];
            if(siz[v]>siz[son[u]]||son[u]==0)
                son[u]=v;
        }
    }
    int js;
    void dfs2(int u,int t) {
        top[u]=t;
        if(son[u])
            dfs2(son[u],t);
        for(int i=head[u]; i; i=a[i].next) {
            int v=a[i].to;
            if(v!=fa[u]&&v!=son[u])
                dfs2(v,v);
        }
    }
    int lca(int x,int y) {
        while(top[x]!=top[y]) {
            if(dep[top[x]]<dep[top[y]])
                swap(x,y);
            x=fa[top[x]];
        }
        return  dep[x]<dep[y]?x:y;
    }
    int bj[1000001];
    int main() {
        int n,m,s,x,y,v,k;
        scanf("%d%d",&n,&m);
        for(int i=1; i<n; i++)
            scanf("%d%d%d",&x,&y,&v),add(x,y,v),add(y,x,v);
        dfs1(1,0,1);
        dfs2(1,1);
        for(int i=1;i<=n;i++)
          for(int j=i+1;j<=n;j++)
                bj[dis[i]+dis[j]-2*dis[lca(i,j)]]=1;
        for(int i=1;i<=m;i++)
            scanf("%d",&k),bj[k]?printf("AYE
    "):printf("NAY
    ");
    }
    

    对于100%
    就要用到点分治了,现在来开始正式讲一讲点分治。

    假设现在k为5。我们可以发现,对于一个根节点,有两种情况会有答案,一种是在他的子树中,另一种是从一个节点到另一个节点并且穿过他。如对于根节点1,有(1,6),(1,4),而对于根节点2(是对于子树的根节点),有(4,5)满足条件。

    会有两种情况满足条件,那么怎么处理?分点?太麻烦了,其实可以把这两种条件看为一种条件。

    1. 如果答案穿过他,分成两条路径,以上图中的(4,5)说,可以看成(2,4)+(2,5)
    2. 如果答案在他的子树中,则可以看成从他的一个子节点到他本身,穿过自己,到达他自己。以(1,4)来说,可以看成(1,4)+(1,1,)。自己到自己的距离为0。

    那么根节点是什么呢?不同的根节点效率会不同

    对于这张图当以1为更节点的时候我们要递归四层,而以3为根节点话只要递归两层。所以要正确选好根节点。那么什么是最好的根节点呢?重心。

    重心

    什么是重心?三角形内三条中线交点?
    但是这里的重心不是数学中的概念。
    树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。

    上面来自百度百科
    实际上重心可以用一句话说明:其所有的子树中最大的子树节点数最少
    那么怎么求重心呢?只要一个树形dp就可以了,接下来直接上代码,相信应该都能理解吧

    在上代码之前先申明一写变量定义

    int n,k;
    int ans[10000001];/*储存答案*/
    int dis[N];/*从当前节点i到枚举当前树的根节点父亲的距离*/(这里随便理解一下吧,我这么说是为了后面的容斥)
    int f[N];/*当以i为根节点时最大子树大小*/
    int vis[N];/*i节点是否被当根使用过*/
    int siz[N];/*以i节点为根时,其子树(包括本身)的节点个数*/
    int root;/*根节点*/
    int sum;/*这棵当前递归的这棵树的大小*/
    
    void findroot(int k,int fa) {
        f[k]=0,siz[k]=1;
        for(int i=head[k]; i; i=a[i].next) {
            int v=a[i].to;
            if(vis[v]||v==fa)
                continue;
            findroot(v,k);
            siz[k]+=siz[v];
            f[k]=max(f[k],siz[v]);
        }
        f[k]=max(f[k],sum-siz[k]);
        if(f[k]<f[root])
            root=k;
    }
    

    对于f[k]=max(f[k],sum-siz[k]);这里有很多人不知道什么意思,我下面来讲一下

    继续用上面的图

    假设你已经递归到了节点2,你的儿子有1,4,5(这是一个无根树)。但是你的递归并不会算1节点,所以需要这一段话来判断他的包含他"父亲"的子树大小是否时最大的。

    当找到重心以后就可以找出每个点与重心的距离.在统计答案就可以了
    对于这道题目可以直接n²的枚举就可以了,但对于别的题,需要别的方法,如二分。
    看看这一题是如何判的

    void calc(int k,int l,int c) {
        tot=0;
        finddep(k,0,l);
        for(int i=1; i<=tot; i++)
            for(int j=1; j<=tot; j++)
                ans[dis[i]+dis[j]]+=c;
    }
    

    但是对于统计答案要注意一点的就是路径会重复算。上图中如果k=7那么对于(1,4),(1,5)这也是个答案,但是这并不是个答案.路径(1,2)被算了两次.所以我们要将重复的路径去掉就可以了

    那么怎么去掉呢?只要每次在递归的时候对于儿子节点,将所有儿子节点的子树满足条件的删掉就可以了,也就是dis和为k,注意这里的dis算的是所有子节点到这个儿子节点父亲的距离.

    void devide(int k) {
        vis[k]=1;
        calc(k,0,1);
        for(int i=head[k]; i; i=a[i].next) {
            int v=a[i].to;
            if(vis[v])
                continue;
            calc(v,a[i].v,-1);//就是这一段话
            root=0,sum=siz[v];
            findroot(v,0);
            devide(root);
        }
    }
    

    接下来上代码

    #include<bits/stdc++.h>
    using namespace std;
    const int N=10001;
    int read() {
        int x=0,f=1;
        char c=getchar();
        while(c<'0'||c>'9')c=='-'?f=-1,c=getchar():c=getchar();
        while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
        return x*f;
    }
    int n,k;
    int ans[10000001];/*储存答案*/
    int dis[N];/*从当前节点i到枚举当前树的根节点父亲的距离*/(这里随便理解一下吧,我这么说是为了后面的容斥)
    int f[N];/*当以i为根节点时最大子树大小*/
    int vis[N];/*i节点是否被当根使用过*/
    int siz[N];/*以i节点为根时,其子树(包括本身)的节点个数*/
    int root;/*根节点*/
    int sum;/*这棵当前递归的这棵树的大小*/
    struct node {
        int next,to,v;
    } a[N<<1];
    int head[N],cnt;
    void add(int x,int y,int c) {
        a[++cnt].to=y;
        a[cnt].next=head[x];
        a[cnt].v=c;
        head[x]=cnt;
    }
    void findroot(int k,int fa) {
        f[k]=0,siz[k]=1;
        for(int i=head[k]; i; i=a[i].next) {
            int v=a[i].to;
            if(vis[v]||v==fa)
                continue;
            findroot(v,k);
            siz[k]+=siz[v];
            f[k]=max(f[k],siz[v]);
        }
        f[k]=max(f[k],sum-siz[k]);
        if(f[k]<f[root])
            root=k;
    }
    int tot;
    void finddep(int k,int fa,int l) {
        dis[++tot]=l;
        for(int i=head[k]; i; i=a[i].next) {
            int v=a[i].to;
            if(v==fa||vis[v])
                continue;
            finddep(v,k,l+a[i].v);
        }
    }
    void calc(int k,int l,int c) {
        tot=0;
        finddep(k,0,l);
        for(int i=1; i<=tot; i++)
            for(int j=1; j<=tot; j++)
                ans[dis[i]+dis[j]]+=c;
    }
    void devide(int k) {
        vis[k]=1;
        calc(k,0,1);
        for(int i=head[k]; i; i=a[i].next) {
            int v=a[i].to;
            if(vis[v])
                continue;
            calc(v,a[i].v,-1);
            root=0,sum=siz[v];
            findroot(v,0);
            devide(root);
        }
    }
    int main() {
        int n=read(),m=read(),x,y,z;
        for (int i=1; i<n; i++)
            x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
        sum=f[0]=n;
        findroot(1,0);
        devide(root);
        for (int i=1; i<=m; i++) {
            int k=read();
            puts(ans[k]?"AYE":"NAY");
        }
        return 0;
    }
    
  • 相关阅读:
    【nyoj-1274】信道安全(SPFA)
    【lightoj-1002】Country Roads(dijkstra变形)
    【牛客练习赛12-B】迷宫(BFS)
    【数论-逆元】复习总结
    【hdu1705】Count the grid(皮克定理)
    CSS 清除默认样式
    vue2.0项目实战使用axios发送请求
    axios基本用法
    vue2.0使用Sortable.js实现的拖拽功能
    vue2.0s中eventBus实现兄弟组件通信
  • 原文地址:https://www.cnblogs.com/hbxblog/p/9826679.html
Copyright © 2011-2022 走看看