• 树分治的初探


    基础内容推荐看:漆子超《分治算法在树的路径问题中的应用》/分治算法在树的路径问题中的应用 的论文

    树的分治的主要分为三种:点、边、链的分治

    基于点的分治

    首先选取一个点将无根树转为有根树,再递归处理每一颗以根结点的儿子为根的子树

    对于点分治,我们选取一个点,要求将其删除以后节点最多的树的节点个数最小,这个点被称为树的重心(这个可以通过树形dp的O(N),可以得到)

    我们以POJ 1741为例:给定一颗n个节点的正权数,u和v之间有路,且路有长度,再给定一个正数k,统计有多少对节点(a,b),他们之间的路径长度<=k

    思路:朴素的暴力需要O(n^2)。然后我们我们仔细观察以后发现,如果把该图变成一棵有根树,那么,所有问题都可以转化成如下三种形式:

    情况一:完全在一棵子树内(注意,这个是一个递归的重要条件)

    情况二:刚好到根节点

    情况三:经过根节点

    所以我们可以发现,情况一完全可以转换成情况二和情况三,所以我们可以利用它进行分治。

    然后说一下部分的细节:我们每次找重心,找到重心以后,以该重心为根,再一次进行dfs,要注意,之前dfs下来以后,目前以该重心进行dfs可能会dfs回去(例如:0-1-2-3-4,我dfs到2是重心了,然后以2为根节点,又会dfs到1去)。之所以会dfs回去的理由是因为,我们遍历所有的节点,让所有的节点都成为一次重心

    其他的细节我就不多说了吧,直接上代码。。。

    挑战程序竞赛的代码:(这个我得注释可能稍微详细一些)

    ///挑战程序竞赛上面的代码。。。貌似因为STL的原因,所以在POJ上跑的超慢?
    //看看会不会爆int! 或者绝对值问题。
    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<string>
    #include<vector>
    #include<algorithm>
    #include<utility>
    #include<iostream>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define ALL(a) a.begin(), a.end()
    #define haha printf("haha
    ")
    const int maxn = 1e6 + 5;
    const int inf = 2000000000;
    struct edge{
        int to, length;
        edge(int to = 0, int len = 0): to(to), length(len){}
    };
    int N, K;
    vector<edge> G[maxn];
    bool centroid[maxn];
    int subtree_size[maxn];
    int ans;
    
    int compute_subtree_size(int v, int p){
        int c = 1;
        for (int i = 0; i < G[v].size(); i++){
            int w = G[v][i].to;
            if (w == p || centroid[w]) continue;
            c += compute_subtree_size(G[v][i].to, v);
        }
        subtree_size[v] = c;
        return c;
    }
    
    pair<int, int> search_centroid(int v, int p, int t){
        pair<int, int> res = mk(inf, -1);
        int s = 1, m = 0;
        for (int i = 0; i < G[v].size(); i++){
            int w = G[v][i].to;
            if (w == p || centroid[w]) continue;
            res = min(res, search_centroid(w, v, t));
            m = max(m, subtree_size[w]);
            s += subtree_size[w];
        }
        m = max(m, t - s);
        res = min(res, mk(m, v));
        return res;
    }
    
    void enumerate_path(int v, int p, int d, vector<int> &ds){
        ds.push_back(d);
        for (int i = 0; i < G[v].size(); i++){
            int w = G[v][i].to;
            if (w == p || centroid[w]) continue;
            enumerate_path(w, v, d + G[v][i].length, ds);
        }
    }
    
    int count_pairs(vector<int> &ds){
        int res = 0;
        sort(ALL(ds));
        int j = ds.size();
        for (int i = 0; i < ds.size(); i++){
            while (j > 0 && ds[i] + ds[j - 1] > K) --j;
            res += j - (j > i ? 1 : 0);
        }
        ///printf("res = %d j = %d
    ", res, j);
        return res / 2;
    }
    
    void solve_subproblem(int v){
        compute_subtree_size(v, -1);
        //printf("v = %d subtree = %d
    ", v, subtree_size[v]);
        int s = search_centroid(v, -1, subtree_size[v]).second;
        centroid[s] = true;
        ///printf("s = %d v = %d
    ", s, v);
        for (int i = 0; i < G[s].size(); i++){
            if (centroid[G[s][i].to]) haha;
            if (centroid[G[s][i].to]) continue;
            solve_subproblem(G[s][i].to);
        }
        vector<int> ds;
        ds.push_back(0);
        for (int i = 0; i < G[s].size(); i++){
            if (centroid[G[s][i].to]) continue;
            vector<int> tds;
            enumerate_path(G[s][i].to, s, G[s][i].length, tds);
            /**
            这里是减去子树上的路径。因为在下面的count_pairs(ds)是统计所有的路径,所以这里要先减去子树上的路径
            **/
            ans -= count_pairs(tds);
            ds.insert(ds.end(), tds.begin(), tds.end());
        }
        ///printf("v = %d
    ", v);
        ans += count_pairs(ds);
        centroid[s] = false;
    }
    
    void solve(){
        ans = 0;
        solve_subproblem(0);
        cout << ans << endl;
    }
    int main(){
        while (scanf("%d%d", &N, &K) && N + K){
            for (int i = 0; i < N; i++) G[i].clear();
            for (int i = 0; i < N - 1; i++){
                int u, v, len;
                scanf("%d%d%d", &u, &v, &len);
                u--, v--;
                G[u].pb(edge(v, len));
                G[v].pb(edge(u, len));
            }
            solve();
    
            for (int i = 0; i < N; i++){
                if (centroid[i]) haha;
            }
    
        }
        return 0;
    }
    View Code

    模仿敲了一遍以后的代码:

    //看看会不会爆int! 或者绝对值问题。
    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<string>
    #include<vector>
    #include<algorithm>
    #include<utility>
    #include<iostream>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define ALL(a) a.begin(), a.end()
    #define haha printf("haha
    ")
    /**
    总体结果有三种情况:
    ①只在一个子树内,所以我们要递归的去找这个子树
    ②刚好到根的距离
    ③经过根
    
    思路:
    ①先计算目前树的节点个数
    ②dfs找到目前树的重心,然后以目前这个重心为根,再次进行dfs
    ③dfs结束以后,再一次计算路径长度
    ④容斥一下,统计最后ans
    **/
    const int maxn = 1e4 + 5;
    const int inf = 0x3f3f3f3f;
    struct Edge{
        int to, val;
        Edge(int t = 0, int val = 0): to(t), val(val){};
    };
    int dp_cnt[maxn], dp_ce[maxn];
    vector<Edge> G[maxn];
    int n, k, ans;
    
    int get_treesize(int u, int par){
        int cnt = 1;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == par || dp_ce[v]) continue;
            cnt += get_treesize(v, u);
        }
        dp_cnt[u] = cnt;
        return cnt;
    }
    
    pair<int, int> get_ce(int u, int par, int rootsize){
        pair<int, int> res = mk(inf, -1);
        int m = 0, s = 1;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == par || dp_ce[v]) continue;
            m = max(dp_cnt[v], m);
            s += dp_cnt[v];
            res = min(res, get_ce(v, u, rootsize));
        }
        m = max(m, rootsize - s);
        res = min(res, mk(m, u));
        return res;
    }
    
    void dfs_dis(int u, int par, int d, vector<int> &tds){
        tds.push_back(d);
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == par || dp_ce[v]) continue;//因为我们最后取出来的是单个点,所以他是有上下重心的,因此我们需要两个限制条件
            dfs_dis(v, u, d + G[u][i].val, tds);
        }
    }
    
    inline int cal(vector<int> &ve){///计算经过根和到根的距离
        int cnt = 0;
        sort(ve.begin(), ve.end());
        int rb = ve.size();
        for(int lb = 0; lb < ve.size(); lb++){
            while (rb && ve[lb] + ve[rb - 1] > k) rb--;
            cnt += rb - (rb > lb ? 1 : 0);
        }
        return cnt / 2;
    }
    
    void dfs_root(int root){
        int rootsize = get_treesize(root, -1);
        int s = get_ce(root, -1, rootsize).second;
        dp_ce[s] = true;
        ///不断分割,直到最后的子节点个数为1,即只有自己本身
        for (int i = 0; i < G[s].size(); i++){
            int v = G[s][i].to;
            if (dp_ce[v]) continue;
            dfs_root(v);
        }
    
        vector<int> ds;
        ds.push_back(0);
        for (int i = 0; i < G[s].size(); i++){
            int v = G[s][i].to;
            if (dp_ce[v]) continue;
            vector<int> tds;
            dfs_dis(v, s, G[s][i].val, tds);
            ans -= cal(tds);
            ds.insert(ds.end(), tds.begin(), tds.end());
        }
        ans += cal(ds);
        dp_ce[s] = false;
    }
    
    int solve(){
        ans = 0;
        dfs_root(0);
        return ans;
    }
    
    int main(){
        while (scanf("%d%d", &n, &k) && n + k > 0){
            for (int i = 0; i < n; i++) G[i].clear();
            for (int i = 0; i < n - 1; i++){
                int u, v, val; scanf("%d%d%d", &u, &v, &val);
                u--, v--;
                G[u].pb(Edge(v, val)); G[v].pb(Edge(u, val));
            }
            printf("%d
    ", solve());
        }
        return 0;
    }
    View Code

    总结一下点分治:

    点分治的主要思路就是寻找重心,然后利用重心来进行不断转移和缩减子节点的大小。注意,最后所有的节点都会变成重心~!

    基于边的分治:

    在树中选取一条边,将原树分成两棵不相交的树,递归处理。 

    基于边的分治,我们选取的边要满足所分离出来的两棵子树的节点个数尽量平均,这条边称为中心边。(该问题也可以通过树形dp来解决)

    再说一下链分治:

    ①点分治  POJ 1741 入门题 在上面点分治的分析中已经讲了

    ②点分治 SPOJ Free tour II 点分治  最多经过k个黑点,所能走的最长长度是多少

    ③点分治or树形dp BZOJ 5152 统计有多少对(u,v)他们的和能被3整除        

    ④点分治+哈希 HDU 4812 找到(u,v)之间,所有val的乘积%(1e6+3)==k的字典序最小的(u,v)

    ⑤点分治(这个我没有用vector了,因为poj的vector跑的太慢了,所以这里我用数组模拟的) POJ 2114

    二:点分治 SPOJ Free tour 2

    题目大意:给你一棵树,有n个点,每两个点之间的路径都有长度,且每个点有黑色和白色,现在有m个黑色的点,问,你最多经过k个黑色的点,所能走的最大距离是多少?

    思路:2009的漆子超的论文里面的题目。关键点就是依据子树的blackcnt的个数进行排序,在进行分治。

    //看看会不会爆int! 或者绝对值问题。
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define ALL(a) a.begin(), a.end()
    #define haha printf("haha
    ")
    /**
    ①先得到目前树的size,找到目前树的重心
    ②然后我们得到所有子树的black的数目,并对其按照black的数目进行排序
    ③然后我们要记录得到在目前black数目能达到的最大深度
    **/
    const int maxn = 400000 + 5;
    const int inf = 1e17;
    struct Edge{
        int to; LL len;
        Edge(int t = 0, LL len = 0): to(t), len(len){}
    };
    vector<Edge> G[maxn];
    int n, k, m;
    int color[maxn], dp_cnt[maxn];
    bool vis[maxn];
    struct Node{
        int id, blackcnt; LL len;
        Node(int id = 0, int b = 0, LL len = 0): id(id), blackcnt(b), len(len){}
        bool operator < (const Node &a) const{
            return a.blackcnt > blackcnt;
        }
    };
    
    int dfs_size(int u, int fa){///没问题
        int cnt = 1;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            cnt += dfs_size(v, u);
        }
        dp_cnt[u] = cnt;
        return cnt;
    }
    
    void dfs_ce(int u, int fa, int &max_cnt, int &ce, int rootsize){///没问题
        int maxval = rootsize - dp_cnt[u];
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            dfs_ce(v, u, max_cnt, ce, rootsize);
            maxval = max(maxval, dp_cnt[v]);
        }
        if (max_cnt > maxval){
            max_cnt = maxval;
            ce = u;
        }
    }
    ///得到有几个黑色的块
    int dfs_black(int u, int fa){
        int c = color[u];
        int maxval = 0;///子树当中最大的                  
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            maxval = max(dfs_black(v, u), maxval);
        }
        return c + maxval;
    }
    
    LL ans, otherlen[maxn], mycntlen[maxn];
    ///计算目前子树的目前颜色数目的最大长度
    void dfs_len(int u, int fa, LL len, int colorcnt){
        mycntlen[colorcnt] = max(len, mycntlen[colorcnt]);
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            dfs_len(v, u, len + G[u][i].len, colorcnt + color[v]);
        }
    }
    
    void dfs(int root){
        int rootsize = dfs_size(root, -1);
        int max_cnt = inf, cetroid;
        dfs_ce(root, -1, max_cnt, cetroid, rootsize);
        vis[cetroid] = true;
        for (int i = 0; i < G[cetroid].size(); i++){
            int v = G[cetroid][i].to;
            if (vis[v]) continue;
            dfs(v);
        }
        vector<Node> subtree;///规定所有的都不算上根的color
        for (int i = 0; i < G[cetroid].size(); i++){
            int v = G[cetroid][i].to;
            if (vis[v]) continue;
            Node a = Node(v, dfs_black(v, cetroid), G[cetroid][i].len);
            subtree.pb(a);
        }
        int alllen = subtree.size();
        if (alllen == 0) {
            vis[cetroid] = false; return ;
        }
        sort(ALL(subtree));
        for (int i = 0; i <= subtree[alllen - 1].blackcnt; i++){
            otherlen[i] = -inf;
        }
        for (int i = 0; i < alllen; i++){
            int bcnt = subtree[i].blackcnt;
            int id = subtree[i].id;
            for (int j = 0; j <= bcnt; j++){ /**把循环中的i改成了j*/
                mycntlen[j] = -inf;
            }
    
            dfs_len(id, cetroid, subtree[i].len, color[id]);///没有包含重心,但是包含了其他的颜色
            if (i > 0){
                for (int j = 0; j <= k - color[cetroid] && j <= bcnt; j++){
                    int c = min(subtree[i - 1].blackcnt, k - color[cetroid] - j);
                    if (otherlen[c] == -inf) break;
                    if (mycntlen[j] != -inf) ans = max(ans, otherlen[c] + mycntlen[j]);
                }
            }
            for (int j = 0; j <= bcnt; j++){
                otherlen[j] = max(otherlen[j], mycntlen[j]);
                if(j) otherlen[j] = max(otherlen[j], otherlen[j - 1]);
                if (j + color[cetroid] <= k) ans = max(ans, otherlen[j]);
            }
        }
        vis[cetroid] = false;
    }
    
    int main(){
        scanf("%d%d%d", &n, &k, &m);
        for (int i = 0; i < m; i++) {
            int u; scanf("%d", &u);
            color[u] = 1;
        }
        for (int i = 0; i < n - 1; i++){
            int u, v; LL val; scanf("%d%d%lld", &u, &v, &val);
            G[u].pb(Edge(v, val)); G[v].pb(Edge(u, val));
        }
        dfs(1);
        printf("%lld
    ", ans);
        return 0;
    }
    /*
    7 1 3
    2
    3
    6
    1 2 3
    2 4 12
    2 5 17
    1 3 5
    3 6 100
    3 7 14
    
    ans:
    29
    
    9 1 3
    2
    3
    6
    1 2 3
    2 4 12
    2 5 17
    1 3 5
    3 6 100
    3 7 14
    6 8 17
    6 9 13
    
    ans:
    30
    */
    View Code

    学习:目前所需要学习的是基本的思路方式。

            这道题所需要学习的就是开另外一个数组来转移保存其他子树的情况

    三:BZOJ 2152 

    题目大意:统计有多少对(u,v),他们之间的距离和%3=0.

    思路:我们把所有的和都%3以后得到的只有0,1,2这三个数,然后我们只要统计0之间的搭配,1和2的相互搭配即可。然后别忘了u==v是存在的,最后加上就好了。然后就是简单的树分治

    800ms

    //看看会不会爆int! 或者绝对值问题。
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define ALL(a) a.begin(), a.end()
    #define haha printf("haha
    ")
    const int maxn = 20000 + 5;
    const int inf = 0x3f3f3f3f;
    struct Edge{
        int to; LL val;
        Edge(int t = 0, LL val = 0): to(t), val(val){}
    };
    bool vis[maxn];
    vector<Edge> G[maxn];
    LL n, ans;
    int dp_cnt[maxn];
     
    int dfs_cnt(int u, int fa){
        int cnt = 1;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            cnt += dfs_cnt(v, u);
        }
        return dp_cnt[u] = cnt;
    }
     
    void dfs_ce(int u, int fa, int &maxcnt, int &cetroid, int treesize){
        int tmp = treesize - dp_cnt[u];
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            tmp = max(tmp, dp_cnt[v]);
            dfs_ce(v, u, maxcnt, cetroid, treesize);
        }
        if (maxcnt > tmp){
            maxcnt = tmp; cetroid = u;
        }
    }
     
    void dfs_len(int u, int fa, int len, vector<int> &tds){
        tds.push_back(len % 3);
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            dfs_len(v, u, len + G[u][i].val, tds);
        }
    }
     
    LL count_pairs(vector<int> &tds){
        sort(ALL(tds));
        LL zero = upper_bound(tds.begin(), tds.end(), 0) - lower_bound(tds.begin(), tds.end(), 0);
        LL one = upper_bound(tds.begin(), tds.end(), 1) - lower_bound(tds.begin(), tds.end(), 1);
        LL two = upper_bound(tds.begin(), tds.end(), 2) - lower_bound(tds.begin(), tds.end(), 2);
        LL cnt = 1LL * zero * (zero - 1);///如果这里写成zero*zero,下面就不需要+n了,因为我已经把原点为0的给放入了
        cnt += one * two * 2;
        return cnt;
    }
     
    void solve(int root){
        int treesize = dfs_cnt(root, -1);
        int maxcnt = inf, cetroid = -1;
        dfs_ce(root, -1, maxcnt, cetroid, treesize);
        //printf("cetroid = %d
    ", cetroid);
        vis[cetroid] = true;
        for (int i = 0; i < G[cetroid].size(); i++){
            int v = G[cetroid][i].to;
            if (vis[v]) continue;
            solve(v);
        }
        vector<int> ds;
        ds.push_back(0);
        for (int i = 0; i < G[cetroid].size(); i++){
            int v = G[cetroid][i].to;
            if (vis[v]) continue;
            vector<int> tds;
            dfs_len(v, cetroid, G[cetroid][i].val, tds);
            ans -= count_pairs(tds);
            ds.insert(ds.end(), ALL(tds));
        }
        ans += count_pairs(ds);
        vis[cetroid] = false;
    }
     
    LL get_gcd(LL a, LL b){
        return b == 0 ? a : get_gcd(b, a % b);
    }
     
    int main(){
        while (scanf("%d", &n) == 1){
            for (int i = 1; i <= n; i++) G[i].clear(), vis[i] = false;
            for (int i = 1; i <= n - 1; i++){
                int u, v, val; scanf("%d%d%d", &u, &v, &val);
                G[u].pb(Edge(v, val)); G[v].pb(Edge(u, val));
            }
            ans = 0;
            solve(1);
            ans = ans + n;
            LL sum = 1LL * n * n;;
            LL gcd = get_gcd(ans, sum);
            printf("%lld/%lld
    ", ans/gcd, sum/gcd);
        }
        return 0;
    }
    View Code

    关键:就事考虑每个数值对凑成3所做的贡献 

    当然这道题目也可以用树形dp做,定义dp[i][3]表示目前为第i个数,他的子树中分别有0几个、1几个、2几个。然后去转移就好了(当然,如果%3改成%10000,那么就不能做了)。

    这里就大致说一下树dp的思路:

    定义:dp[i][3]表示目前的root是i,他%3以后分别为0、1、2的余数的个数。

    转移为:假设u->v的长度为val,暴力枚举j(0=<j<=2),dp[u][(j + val) %3] += dp[v][val];

    然后每次统计的时候分两步

    ①统计目前该子树上的0的个数

    ②统计目前该子树和其他子树能组成0的个数

    350ms

    //看看会不会爆int! 或者绝对值问题。
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define ALL(a) a.begin(), a.end()
    #define haha printf("haha
    ")
    const int maxn = 20000 + 5;
    struct Edge{
        int to; LL val;
        Edge(int t = 0, LL val = 0): to(t), val(val){}
    };
    LL dp[maxn][3], a[3];
    int n;
    LL ans;
    vector<Edge> G[maxn];
    
    void dfs_down(int u, int fa){
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa) continue;
            dfs_down(v, u);
            int len = G[u][i].val % 3;
            a[len]++;
            for (int j = 0; j < 3; j++){
                int tmp = (j + len) % 3;
                a[tmp] += dp[v][j];
            }
            ans += a[0];
            ans += 1LL * a[0] * dp[u][0] + 1LL * a[1] * dp[u][2] + 1LL * a[2] * dp[u][1];
            for (int j = 0; j < 3; j++){
                dp[u][j] += a[j];
            }
            memset(a, 0, sizeof(a));
        }
    }
    
    LL get_gcd(LL a, LL b){
        return b == 0 ? a : get_gcd(b, a % b);
    }
    
    int main(){
        while (scanf("%d", &n) == 1){
            for (int i = 1; i <= n; i++) G[i].clear();
            for (int i = 1; i <= n - 1; i++){
                int u, v, val; scanf("%d%d%d", &u, &v, &val);
                G[u].pb(Edge(v, val)); G[v].pb(Edge(u, val));
            }
            memset(dp, 0, sizeof(dp));
            ans = 0;
            dfs_down(1, -1);
            ans = ans * 2 + n;
            LL sum = 1LL * n * n;;
            LL gcd = get_gcd(ans, sum);
            printf("%lld/%lld
    ", ans/gcd, sum/gcd);
        }
        return 0;
    }
    View Code

    四:点分治+哈希 hdu4812

    题目大意:找到(u,v)之间,所有val的乘积%(1e6+3) == k的字典序最小的(u,v),如果没有输出no solution

    思路:我们因为我们每次都只能得到x,然而y如果用暴力去枚举的话,肯定TLE,所以我们这里就想到了哈希。因为我们能得到x,然后y不知道,但是树分治会在之前得到一个集合,所以说,我们可以先预处理处1/x的逆元,然后k/x就可以知道了,然后令y=k/x,然后再在集合里面找一下就行了。总体感觉不难。

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    const int maxn = 1e5 + 5;
    const int inf = 0x3f3f3f3f;
    const LL mod = 1e6 + 3;
    LL myhash[mod + 5], val[maxn];
    int n;
    LL k;
    vector<int> G[maxn];
    
    LL cal(LL x, LL n){
        LL ans = 1;
        while (n){
            if (n & 1) ans = ans * x % mod;
            x = x * x % mod;
            n >>= 1;
        }
        return ans;
    }
    pair<int, int> ans;
    bool vis[maxn];
    int dp_cnt[maxn];
    
    int dfs_cnt(int u, int fa){
        int cnt = 1;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa || vis[v]) continue;
            cnt += dfs_cnt(v, u);
        }
        return dp_cnt[u] = cnt;
    }
    
    void dfs_ce(int u, int fa, int &maxcnt, int &cetroid, int treesize){
        int tmp = treesize - dp_cnt[u];
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa || vis[v]) continue;
            tmp = max(tmp, dp_cnt[v]);
            dfs_ce(v, u, maxcnt, cetroid, treesize);
        }
        if (maxcnt > tmp){
            maxcnt = tmp; cetroid = u;
        }
    }
    
    void dfs(int u, int fa, LL treeval, vector<pair<LL, pair<int, int> > > &tds, int cetroid){
        tds.push_back(mk(treeval, mk(min(cetroid, u), max(cetroid, u))));
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa || vis[v]) continue;
            dfs(v, u, treeval * val[v] % mod, tds, cetroid);
        }
    }
    LL a[maxn];
    void find_answer(vector<pair<LL, pair<int, int> > > &ds, vector<pair<LL, pair<int, int> > > tds, int cetroid){
        sort(ALL(ds));
        for (int i = 0; i < ds.size(); i++) a[i] = ds[i].fi;
        for (int i = 0; i < tds.size(); i++){
            LL val = tds[i].fi;
            LL needval = myhash[val] * k % mod;
            int pos = lower_bound(a, a + ds.size(), needval) - a;
            if (a[pos] != needval) continue;
            pair<int, int> ta = ds[pos].second, tb = tds[i].second;
            int pa = ta.first == cetroid ? ta.second : ta.first;
            int pb = tb.first == cetroid ? tb.second : tb.first;
            ans = min(ans, mk(min(pa, pb), max(pa, pb)));
        }
    }
    
    void solve(int root){
        int treesize = dfs_cnt(root, -1);
        int maxcnt = inf, cetroid = -1;
        dfs_ce(root, -1, maxcnt, cetroid, treesize);
        vis[cetroid] = true;
        for (int i = 0; i < G[cetroid].size(); i++){
            int v = G[cetroid][i];
            if (vis[v]) continue;
            solve(v);
        }
        LL treeval = val[cetroid] % mod;
        vector<pair<LL, pair<int, int> > > ds;
        ds.push_back(mk(treeval, mk(cetroid, cetroid)));
        for (int i = 0; i < G[cetroid].size(); i++){///应该是目前子树和和其他子树的相比
            int v = G[cetroid][i];
            if (vis[v]) continue;
            vector<pair<LL, pair<int, int> > >tds;
            dfs(v, cetroid, val[v] % mod, tds, cetroid);
            find_answer(ds, tds, cetroid);
            for (int i = 0; i < tds.size(); i++) {
                tds[i].fi = tds[i].fi * treeval % mod;
                if (tds[i].first == k) ans = min(ans, tds[i].second);
            }
            ds.insert(ds.end(), ALL(tds));
        }
        vis[cetroid] = false;
    }
    
    int main(){
        for (int i = 0; i < mod; i++)
            myhash[i] = cal(1LL * i, mod - 2);
        while (scanf("%d%I64d", &n, &k) == 2){
            for (int i = 1; i <= n; i++){
                scanf("%I64d", val + i);
                G[i].clear();
                vis[i] = false;
            }
            for (int i = 0; i < n - 1; i++){
                int u, v; scanf("%d%d", &u, &v);
                G[u].pb(v); G[v].pb(u);
            }
            ans = mk(inf, inf);
            solve(1);
            if (ans.fi == inf) printf("No solution
    ");
            else {
                printf("%d %d
    ", ans.fi, ans.se);
            }
        }
        return 0;
    }
    View Code

    五:POJ 2114

    题目大意:一棵树n个点,每条边有权值val,有m个询问,m<=100,每次输入大于0的询问,输入0则跳出。问,这棵树中有无两个点(u,v)之间的权值为k?

    思路:表示用vector再POJ上T我一脸。STL跑的真心慢。然后感觉也没啥的吧,就是暴力一下tds数组,然后lowerbound找ds数组即可。复杂度为O(n*m*(logn)^2)

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<string>
    #include<vector>
    #include<algorithm>
    #include<utility>
    #include<iostream>
    using namespace std;
    #pragma comment(linker,"/STACK:102400000,102400000")
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    const int maxn = 10000 + 5;
    struct Node{
        int to, val;
        Node(int to = 0, int val = 0): to(to), val(val){}
    };
    vector<Node> G[maxn];
    int n, k;
    bool vis[maxn];
    int dp[maxn], myk[maxn];
    
    int dfs_cnt(int u, int fa){
        int cnt = 1;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (vis[v] || v == fa) continue;
            cnt += dfs_cnt(v, u);
        }
        return dp[u] = cnt;
    }
    
    void dfs_ce(int u, int fa, int &maxcnt, int &ce, int treesize){
        int cnt = treesize - dp[u];
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (vis[v] || v == fa) continue;
            cnt = max(cnt, dp[v]);
            dfs_ce(v, u, maxcnt, ce, treesize);
        }
        if (maxcnt > cnt){
            maxcnt = cnt;
            ce = u;
        }
    }
    
    int dslen, tdslen, tds[maxn], ds[maxn];
    void dfs_len(int u, int fa, int val){
        tds[tdslen++] = val;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa || vis[v]) continue;
            dfs_len(v, u, val + G[u][i].val);
        }
    }
    
    bool findtds(){
        sort(ds, ds + dslen);
        sort(tds, tds + tdslen);
        ///sort(tds.begin(), tds.end());
        ///sort(ds.begin(), ds.end());
        int val = k;
        for (int i = 0; i < tdslen; i++){
            if (tds[i] > val) break;
            int findk = val - tds[i];
            if (findk == 0){
                return true;
            }
            int pos = lower_bound(ds, ds + dslen, findk) - ds;
            if (pos < dslen && ds[pos] == findk) {
                return true;
            }
        }
        for (int i = 0; i < tdslen; i++){
            ds[dslen++] = tds[i];
        }
        return false;
    }
    
    bool dfs(int root){
        int treesize = dfs_cnt(root, -1);
        int maxcnt = maxn, cetroid;
        dfs_ce(root, -1, maxcnt, cetroid, treesize);
        ///printf("cetroid = %d maxcnt = %d treesize = %d
    ", cetroid, maxcnt, treesize);
        vis[cetroid] = true;
        for (int i = 0; i < G[cetroid].size(); i++){
            int v = G[cetroid][i].to;
            if (vis[v]) continue;
            if(dfs(v)) {
                vis[cetroid] = false;
                return true;
            }
        }
        dslen = 0;
        for (int i = 0; i < G[cetroid].size(); i++){
            int v = G[cetroid][i].to;
            if (vis[v]) continue;
            tdslen = 0;
            dfs_len(v, cetroid, G[cetroid][i].val);
            if(findtds()) {
                vis[cetroid] = false;
                return true;
            }
        }
        vis[cetroid] = false;
        return false;
    }
    
    int main(){
        while (scanf("%d", &n) && n){
            for (int i = 1; i <= n; i++) G[i].clear();
            for (int i = 1; i <= n; i++){
                int to, val;
                while (scanf("%d", &to) && to != 0){
                    scanf("%d", &val);
                    G[i].push_back(Node(to, val));
                    G[to].push_back(Node(i, val));
                }
            }
            while (scanf("%d", &k) && k){
                bool flag = dfs(1);
                if (flag) printf("AYE
    ");
                else printf("NAY
    ");
            }
            printf(".
    ");
        }
        return 0;
    }
    View Code

    六:

    七:

    八:

    九:

    十:

    十一:

    十二:

    十三:

    十四:

    十五:

    十六:

    十七:

    十八:

    十九:

    二十:

  • 相关阅读:
    NOI2018 你的名字——SAM+线段树合并
    [NOI2008]假面舞会——数论+dfs找环
    CF1037H Security——SAM+线段树合并
    CF700E Cool Slogans——SAM+线段树合并
    CF666E Forensic Examination——SAM+线段树合并+倍增
    [BJOI2019]光线——递推
    ProjectEuler215 Crack-free Walls
    ProjectEuler237 Tours on a 4 x n playing board
    [SCOI2016]美味——主席树+按位贪心
    [ZJOI2013]K大数查询——整体二分
  • 原文地址:https://www.cnblogs.com/heimao5027/p/6009011.html
Copyright © 2020-2023  润新知