zoukankan      html  css  js  c++  java
  • 树分治入门 POJ1741,hdu5977

    经常发现一些布布扣一类的网站直接爬csdn的blog,娘的

    原文地址在这http://blog.csdn.net/cww97/article/details/77506430

    树分治讲解

    对于树上的路径问题,一种高效的处理方式就是分治算法。关于树分治算法的研究,详见2009年IOI国家集训队论文——《分治算法在树的路径问题中的应用》。
    通常对于树上的分治算法有两种,第一种是针对点进行的分治,另一种是针对边进行的分治,可以证明,大部分情况下点分治算法的性能更加稳定,而边分治在某些情况下,算法效率非常低。所以以下主要讨论点分治。
    如POJ-1741,求解一棵树中路径长度不大于K的有多少点对。
    对于一棵有根树,树中满足对所对应的一条路径,必然是以下两种情况之一:
    1.经过根节点
    2.路径在以根节点某个儿子为根的一棵子树中
    对于情况2,可以递归求解,下面主要来考虑情况1.
    那么对于这道题的情况1,设dis[i]为节点i到根的距离,我们就是要求解有多少对经过根的路径,那么问题等价于能找到多少对不同的点对(i,j),使得dis[i]+dis[j]<=k,而且i和j要属于以当前根的两个不同的儿子为根的子树中。
    将问题转化以下,就可以发现所求结果等价于在一棵有根树中找到的点对数x-在以当前根的儿子为根的子树所找到的点对数。
    求X、Y的过程均可以转化为以下问题:已知A[1],A[2],…A[m],求满足i

    poj1741

    论文里第一道例题

    参考了网上的两个板

    kuangbin的solve写的很csy,这一部分用了这个

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 1e4 + 7;
    const int INF = 0x3f3f3f3f;
    int n, k, ans;
    
    struct Edge{
        int from, to, w, nxt;
        Edge(){}
        Edge (int f, int t, int _w, int n):from(f),to(t),w(_w),nxt(n){}
    } edges[N * 2];
    bool vis[N];
    int head[N], E, siz[N], dep[N];
    
    void Init(){
        E = 0;
        memset(head, -1, sizeof(head));
        memset(vis, false, sizeof(vis));
    }
    void AddEdge(int u,int v,int w){
        edges[E] = Edge(u, v, w, head[u]);
        head[u] = E++;
    }
    
    int dfssize(int u, int pre){
        siz[u] = 1;
        for(int i = head[u]; i != -1; i = edges[i].nxt){
            Edge &e = edges[i];
            if(e.to == pre || vis[e.to])continue;
            siz[u] += dfssize(e.to, u);
        }
        return siz[u];
    }
    
    //找重心
    void dfsroot(int u, int pre, int totnum, int &minn, int &root){
        int maxx = totnum - siz[u];
        for (int i = head[u]; i != -1;i = edges[i].nxt){
            Edge &e = edges[i];
            if(e.to == pre || vis[e.to]) continue;
            dfsroot(e.to, u, totnum, minn, root);
            maxx = max(maxx, siz[e.to]);
        }
        if(maxx < minn){minn = maxx; root = u;}
    }
    
    //求每个点离重心的距离
    void dfsdep(int u,int pre,int dist, int &num){
        dep[num++] = dist;
        for(int i = head[u]; i != -1; i = edges[i].nxt){
            Edge &e = edges[i];
            if(e.to == pre || vis[e.to])continue;
            dfsdep(e.to, u, dist + e.w, num);
        }
    }
    
    //计算以u为根的子树中有多少点对的距离小于等于K
    int calc(int u, int d){
        //printf("calc(%d, %d)
    ", u, d);
        int ans = 0, num = 0;
        dfsdep(u, -1, d, num);
        sort(dep, dep + num);
        int i = 0, j = num - 1;
        for (; i < j; i++){
            while (dep[i]+dep[j]>k && i<j) j--;
            ans += j - i;
        }
        return ans;
    }
    
    void solve(int u){
        int Max = N, root, minn = INF;
        int totnum = dfssize(u, -1);
        dfsroot(u, -1, totnum, minn, root);
        ans += calc(root, 0);
        vis[root] = 1;
        for(int i = head[root]; i != -1; i = edges[i].nxt){
            Edge &e = edges[i];
            if (vis[e.to]) continue;
            ans -= calc(e.to, e.w);
            solve(e.to);
        }
    }
    
    int main(){
        ///freopen("in.txt","r",stdin);
        int u, v, w;
        for (; ~scanf("%d%d", &n, &k) && (n|k);){
            Init();
            for(int i = 1; i < n; i++){
                scanf("%d%d%d", &u, &v, &w);
                AddEdge(u, v, w);
                AddEdge(v, u, w);
            }
            ans = 0;
            solve(1);
            printf("%d
    ", ans);
        }
        return 0;
    }
    

    2016ICPC大连 hdu5977

    现场的时候k = 7,树形dp可以水过,重现赛的时候k改为了10

    需要树分治,裸的树分治(如上),统计距离小于等于k的点对数

    本题问路径经包含所有颜色的点对数

    利用位运算状压,k种颜色,k<=10,每种颜色占一位

    原题转化为统计路径 or运算和 == fullk的点对数

    fullk = (1<<k) - 1;

    poj1741通过一个sort和两个指针统计出了dist<=k的点对数

    这里是二进制位,sort不灵了,需要另一种nlogn的方案

    对于每点u的状态dep[u],枚举其每个子集s0,ans += hash[fullk ^ so]

    枚举子集一开始也不会,很玄学,这里有一些讲解,骚到了

    这里写图片描述

    位运算状压其实很好想,把原来的加号改成or运算就好了

    很骚的一段就是calc中的一段,从这里再次盗个图来,举了个枚举子集的例子

    这里写图片描述

    我见他代码第一行是

    /**此代码借鉴了某大神的,我看懂了后又分析的*/

    似乎他也参考了这个blog

    扯了一堆有的没的

    最后贴自己的代码吧,用上题的板子改,dfsdep和dfs看着改就好,calc动些脑子

    #include <map>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    typedef long long LL;
    const int N = 5e4 + 7;
    const int INF = 0x3f3f3f3f;
    int n, k;
    LL ans;
    int col[N], fullk;
    LL hashS[1200];
    
    struct Edge{
        int from, to, w, nxt;
        Edge(){}
        Edge (int f, int t, int _w, int n):from(f),to(t),w(_w),nxt(n){}
    } edges[N * 2];
    bool vis[N];
    //本题中边无w,dep为节点到root col or运算和
    int head[N], E, siz[N], dep[N];
    
    void Init(){
        E = 0; fullk = (1<<k) - 1;
        memset(head, -1, sizeof(head));
        memset(vis, false, sizeof(vis));
    }
    void AddEdge(int u,int v,int w){
        edges[E] = Edge(u, v, w, head[u]);
        head[u] = E++;
    }
    
    int dfssize(int u, int pre){
        siz[u] = 1;
        for(int i = head[u]; i != -1; i = edges[i].nxt){
            Edge &e = edges[i];
            if(e.to == pre || vis[e.to])continue;
            siz[u] += dfssize(e.to, u);
        }
        return siz[u];
    }
    
    //找重心
    void dfsroot(int u, int pre, int totnum, int &minn, int &root){
        int maxx = totnum - siz[u];
        for (int i = head[u]; i != -1;i = edges[i].nxt){
            Edge &e = edges[i];
            if(e.to == pre || vis[e.to]) continue;
            dfsroot(e.to, u, totnum, minn, root);
            maxx = max(maxx, siz[e.to]);
        }
        if(maxx < minn){minn = maxx; root = u;}
    }
    
    //求每个点离重心的距离
    void dfsdep(int u, int pre, int dist, int &num){
        //printf("u = %d, num = %d, dist = %d
    ", u, num, dist);
        dep[num++] = dist;
        for(int i = head[u]; i != -1; i = edges[i].nxt){
            Edge &e = edges[i];
            if(e.to == pre || vis[e.to])continue;
            dfsdep(e.to, u, dist | (1<<col[e.to]), num);
        }
    }
    
    //计算以u为根的子树中有多少点对的距离小于等于K, 经过u
    LL calc(int u, int d){ //d为基础距离
        //printf("calc(%d, %d)
    ", u, d);
        LL ans = 0;
        int num = 0;
        dfsdep(u, -1, d, num);
        memset(hashS, 0, sizeof(hashS));
        for (int i = 0; i < num; i++) hashS[dep[i]]++;
        for (int i = 0; i < num; i++){
            hashS[dep[i]]--;
            ans += hashS[fullk];
            for (int s0 =  dep[i]; s0; s0 = (s0-1) & dep[i]){
                ans += hashS[fullk ^ s0];
            }
            hashS[dep[i]]++;
        }
        return ans;
    }
    
    void dfs(int u){
        int root, minn = INF;
        int totnum = dfssize(u, -1);
        dfsroot(u, -1, totnum, minn, root);
        //printf("---------> root = %d
    ", root);
        ans += calc(root, 1<<col[root]);
        //printf("ans = %d
    ", ans);
        vis[root] = 1;
        for(int i = head[root]; i != -1; i = edges[i].nxt){
            Edge &e = edges[i];
            if (vis[e.to]) continue;
            ans -= calc(e.to, (1<<col[root]) | (1<<col[e.to])); //减去重复部分,即没必要经过u的
            //printf("ans = %d
    ", ans);
            dfs(e.to);                             //在下一层dfs中解决
        }
    }
    
    int main(){
        //freopen("in.txt", "r", stdin);
        int u, v;
        for (; scanf("%d%d", &n, &k) == 2;){
            Init();
            for (int i = 1; i <= n; i++) {
                scanf("%d", &col[i]);
                col[i]--;
            }
            for (int i = 1; i < n; i++){
                scanf("%d%d", &u, &v);
                AddEdge(u, v, 0);
                AddEdge(v, u, 0);
            }
            ans = 0;
            if (k == 1) ans = (LL)n * (LL)n;
            else dfs(1);
            printf("%lld
    ", ans);
        }
        return 0;
    }
    

    一个子集枚举的小题目

    poj2531,给个20个点的图,问最小割

    还有点迷糊这个怎么就能剪枝了

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    int main(){
        int n, g[20][20];
        scanf("%d", &n);
        for(int i = 0; i < n; i++)
            for(int j = 0; j < n; j++)
                scanf("%d", &g[i][j]);
        int ans = 0;
        //剪枝就是把i++改成了i+=2,让i总是奇数
        for(int i = 1; i < (1<<n); i += 2){
            int sum = 0;
            for (int j = 0; j < n; j++) if (i & (1<<j)){
                for(int k = 0; k < n; k++)
                    if ((~i) & (1<<k)) sum += g[j][k];
            }
            ans = max(ans, sum);
        }
        printf("%d
    ", ans);
        return 0;
    }

    这题正解好像是dfs

    不过,随机化乱搞似乎也可以,乱搞好玩啊,here

    操作1e5次
    对于每一次操作,随机选择一个点,把他放到另一个集合里面去,然后for一遍,算出当前的割值sum,再与记录的最大值ans比较更新

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int n, g[22][22];
    bool go[22];
    
    int main(){
        for (; ~scanf("%d", &n);){
            for (int i = 0; i < n; i++)
                for (int j = 0; j < n; j++)
                    scanf("%d", &g[i][j]);
    
            memset(go, 0, sizeof(0));
            int _ = 1e5, sum = 0, ans = 0;
            for (; _--;){
                int t = rand() % n;
                go[t] ^= 1;
                for (int i = 0; i < n; i++){
                    if (go[t] ^ go[i]) sum += g[t][i];
                    else sum -= g[t][i];
                }
                ans = max(sum, ans);
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    
  • 相关阅读:
    栈的使用
    学习
    JS中常用的工具类
    AOP的相关概念
    Git-用git同步代码
    权限管理3-整合Spring Security
    权限管理2-开发权限管理接口
    权限管理1-需求描述
    使用Canal作为mysql的数据同步工具
    使用存储过程在mysql中批量插入数据
  • 原文地址:https://www.cnblogs.com/cww97/p/7533945.html
Copyright © 2011-2022 走看看