zoukankan      html  css  js  c++  java
  • 树上差分

    树上差分

    点差分

    • cf[maxn] cf[i]记录点i被经过了多少次

    • 当s->t最短路径上每个点都被经过一次,则:

      cf[s]++;
      cf[t]++;
      cf[lca(s,t)]--;
      cf[ father[lca(s,t)] ]--;
      

    边差分

    • cf[maxn] 记录点i到其父节点的边被经过了多少次

    • 当s->t最短路径上每个边都被经过一次,则:

      cf[s]++;
      cf[t]++;
      cf[lca(s,t)] -= 2;
      

    核心算法

    • LCA,可以用倍增法,在线求LCA(s,t),

      int depth[maxn];//记录每个结点的深度 根节点深度为0
      int fa[maxn][20];//fa[i][j]表示i结点向上爬 2^j是哪个结点
      //预处理出每个结点的深度和其直接父节点
      void dfs(int u, int pre, int d){
          fa[u][0] = pre;//向上一个当然是直接父节点 pre
          depth[u] = d;//深度
          for(int i=head[u]; i; i=edge[i].nxt){
              int v = edge[i].v;
              if(v != pre){
                  //求点到树根的距离
                  dist[v] = dist[u] + edge[i].w;
                  dfs(v , u, d+1);
              }
          }
      }
      //倍增预处理出fa数组
      void init(){
          //有点区间DP的意思
          for(int j=0; (1<<(j+1))<n; j++){
              for(int i=1; i<=n; i++){
                  if(fa[i][j] < 0) fa[i][j+1] = -1;
                  else fa[i][j+1] = fa[fa[i][j]][j];
              }
          }
      }
      //给定俩个结点在线求其LCA
      int LCA(int u, int v){
          //保证v是较深的点
          if(depth[u] > depth[v]) swap(u , v);
          int temp = depth[v] - depth[u];//深度差
          //先把v调到与u等高处
          for(int i=0; (1<<i)<=temp; i++){
              if((1<<i) & temp) v = fa[v][i];
          }
          if(u==v) return u;
          //然后两个人比翼双飞
          for(int i=(int)(log(1.0*n)/log(2.0)); i >= 0; i--){
              if(fa[u][i] != fa[v][i]){
                  u = fa[u][i];
                  v = fa[v][i];
              }
          }
          return fa[u][0];
      
      }
      
    • DFS遍历树得到差分数组,得到cf[i] (这个顶点或者边被经过了多少次)

      //遍历差分 
      void dfs2(int u, int f){
          for(int i=head[u]; i; i=edge[i].nxt) {
              int v = edge[i].v;
              if(v == f) continue;
              dfs2(v , u);
              cf[u] += cf[v];
          }
      }
      

    例题

    • 洛谷 P3128 [USACO15DEC]最大流Max Flow

    • 题意:点差分板子题

      //LCA 倍增 大家一起跳跳跳
      #include<iostream>
      #include<queue>
      #include<list>
      #include<vector>
      #include<cstring>
      #include<set>
      #include<stack>
      #include<map>
      #include<cmath>
      #include<algorithm>
      #include<string>
      #include<stdio.h>
      using namespace std;
      typedef long long ll;
      #define MS(x,i) memset(x,i,sizeof(x))
      #define rep(i,s,e) for(int i=s; i<=e; i++)
      #define sc(a) scanf("%d",&a)
      #define scl(a) scanf("%lld",&a)
      #define sc2(a,b) scanf("%d %d", &a, &b)
      #define debug printf("debug......
      ");
      #define pfd(x) printf("%d
      ",x)
      #define pfl(x) printf("%lld
      ",x)
      const double eps=1e-8;
      const double PI = acos(-1.0);
      const int inf = 0x3f3f3f3f;
      const ll INF = 0x7fffffff;
      const int maxn = 5e4+10;
      int dx[4] = {0, 0, 1, -1};
      int dy[4]  = {1, -1, 0 , 0};
      
      int t,n,k;
      vector<int> G[maxn];
      int s,e,lca;
      int depth[maxn];//记录每个结点的深度 根节点深度为0
      int fa[maxn][20];//fa[i][j]表示i结点向上爬 2^j是哪个结点
      
      int ans = -1;//最终答案 
      int value[maxn];//每个结点的点权 
      
      
      //预处理出每个结点的深度和其直接父节点
      void dfs(int u, int pre, int d){
          fa[u][0] = pre;//向上一个当然是直接父节点 pre
          depth[u] = d;//深度
          for(int i=0; i<G[u].size(); i++){
              int v = G[u][i];
              if(v != pre){
                  dfs(v , u, d+1);
              }
          }
      }
      //倍增预处理出fa数组
      void init(){
          //有点区间DP的意思
          for(int j=0; (1<<(j+1))<n; j++){
              for(int i=1; i<=n; i++){
                  if(fa[i][j] < 0) fa[i][j+1] = -1;
                  else fa[i][j+1] = fa[fa[i][j]][j];
              }
          }
      }
      //给定俩个结点在线求其LCA
      int LCA(int u, int v){
          //保证v是较深的点
          if(depth[u] > depth[v]) swap(u , v);
          int temp = depth[v] - depth[u];//深度差
          //先把v调到与u等高处
          for(int i=0; (1<<i)<=temp; i++){
              if((1<<i) & temp) v = fa[v][i];
          }
          if(u==v) return u;
          //然后两个人比翼双飞
          for(int i=(int)(log(1.0*n)/log(2.0)); i >= 0; i--){
              if(fa[u][i] != fa[v][i]){
                  u = fa[u][i];
                  v = fa[v][i];
              }
          }
          return fa[u][0];
      
      }
      
      //遍历差分 
      void dfs2(int u, int f){
          for(int i=0; i<G[u].size(); i++){
              int v = G[u][i];
              if(v == f) continue;
              dfs2(v , u);
              value[u] += value[v];
          }
          if(value[u] > ans) ans = value[u];
      }
      
      
      int main(){
          int u,v;
          while(sc2(n,k) != EOF){
              MS(value , 0);
              rep(i , 1, n) G[i].clear();
              rep(i , 1, n-1){
                  sc2(u,v);
                  G[u].push_back(v);
                  G[v].push_back(u);
              }
              dfs(1 , 0 , 1) ;//初始化LCA 
              init(); 
              for(int i=1; i<=k; i++){
                  sc2(u , v);
                  value[u]++;
                  value[v]++;
                  int lca = LCA(u,v);
                  value[lca]--;
                  value[fa[lca][0]]--;
              }
              
              dfs2(1 , 0);
              pfd(ans);
          }
          return 0;
      }
      
      
    • 洛谷 P2680 运输计划 (边差分)

    • 题意: 给定一棵N个结点的带权树,以及K条路径,现在可以使某一个边的权值变为0,使某条路径的总长度变小,问这些路径的最大值的最小值是多少?

    • 思路:

      • 先找出最长路径mx和最长边me,那么答案ans必然处于[mx-me , mx]之间

      • 二分答案ans,什么时候ans是合法的呢?一是ans>=mx,必然合法;而是若有ct条路径均大于ans,想要使得减去某一条边后这ct条路径各自长度都小于ans,则必然是存在一条长度为len的公共边,且len >= mx - ans(这ct条路径中的最长路径减去这条边后路径长不超过ans)

      • 什么时候它才是公共边?那就是对这ct条边进行差分,进行cf操作后跑dfs,然后看是否存在一条边cf[i] = ct && len[i] >= mx - ans

        //LCA 倍增 大家一起跳跳跳
        #include<iostream>
        #include<queue>
        #include<list>
        #include<vector>
        #include<cstring>
        #include<set>
        #include<stack>
        #include<map>
        #include<cmath>
        #include<algorithm>
        #include<string>
        #include<stdio.h>
        using namespace std;
        typedef long long ll;
        #define MS(x,i) memset(x,i,sizeof(x))
        #define rep(i,s,e) for(int i=s; i<=e; i++)
        #define sc(a) scanf("%d",&a)
        #define scl(a) scanf("%lld",&a)
        #define sc2(a,b) scanf("%d %d", &a, &b)
        #define debug printf("debug......
        ");
        #define pfd(x) printf("%d
        ",x)
        #define pfl(x) printf("%lld
        ",x)
        const double eps=1e-8;
        const double PI = acos(-1.0);
        const int inf = 0x3f3f3f3f;
        const ll INF = 0x7fffffff;
        const int maxn = 3e5+10;
        int dx[4] = {0, 0, 1, -1};
        int dy[4]  = {1, -1, 0 , 0};
        
        int t,n,k;
        
        int head[maxn];//head数组 
        int cnt;//边数 
        int dist[maxn];//点i到根节点的路径长度 
        int cf[maxn];//cf[i]表示i到father[i]这条边被经过了多少次
        int dis[maxn];//dis[i]表示第i个路径的总长度 
        int mx;//最长路径长度 
        int me;//最长边 
        int ans;//最终结果 
        struct node{
            int v;
            int nxt;
            int w;
        }edge[maxn<<1];
        
        int lca[maxn]; //存第i个路径两端点的LCA 
        int depth[maxn];//记录每个结点的深度 根节点深度为0
        int fa[maxn][20];//fa[i][j]表示i结点向上爬 2^j是哪个结点
        int l[maxn],r[maxn];//存路径端点 
        
        void Init(){
            cnt = 1;
            mx = me = -1; 
            rep(i , 0 , n){
                head[i] = 0;
                dis[i] = 0; 
            }
        }
        void addEdge(int u, int v, int w){
            edge[cnt].v = v;
            edge[cnt].w = w;
            edge[cnt].nxt = head[u];
            head[u] = cnt++;
        }
        //预处理出每个结点的深度和其直接父节点
        void dfs(int u, int pre, int d){
            fa[u][0] = pre;//向上一个当然是直接父节点 pre
            depth[u] = d;//深度
            for(int i=head[u]; i; i=edge[i].nxt){
                int v = edge[i].v;
                if(v != pre){
                    dist[v] = dist[u] + edge[i].w;
                    dfs(v , u, d+1);
                }
            }
        }
        //倍增预处理出fa数组
        void init(){
            //有点区间DP的意思
            for(int j=0; (1<<(j+1))<n; j++){
                for(int i=1; i<=n; i++){
                    if(fa[i][j] < 0) fa[i][j+1] = -1;
                    else fa[i][j+1] = fa[fa[i][j]][j];
                }
            }
        }
        //给定俩个结点在线求其LCA
        int LCA(int u, int v){
            //保证v是较深的点
            if(depth[u] > depth[v]) swap(u , v);
            int temp = depth[v] - depth[u];//深度差
            //先把v调到与u等高处
            for(int i=0; (1<<i)<=temp; i++){
                if((1<<i) & temp) v = fa[v][i];
            }
            if(u==v) return u;
            //然后两个人比翼双飞
            for(int i=(int)(log(1.0*n)/log(2.0)); i >= 0; i--){
                if(fa[u][i] != fa[v][i]){
                    u = fa[u][i];
                    v = fa[v][i];
                }
            }
            return fa[u][0];
        
        }
        
        //遍历差分 
        void dfs2(int u, int f){
            for(int i=head[u]; i; i=edge[i].nxt) {
                int v = edge[i].v;
                if(v == f) continue;
                dfs2(v , u);
                cf[u] += cf[v];
            }
        }
        
        //这条边必须出现了ct次, 并且它的长度不小于len 
        bool dfsCheck(int u, int f, int ct, int len){
            for(int i=head[u]; i ; i=edge[i].nxt){
                int v = edge[i].v;
                if(v == f) continue;
                if(cf[v] == ct && edge[i].w >= len) return 1;
                if(dfsCheck(v , u, ct , len)) return 1;
            } 
            return 0;//根节点返回还找不到,那就不存在这样的公共边 
        }
        
        bool judge(int ans){
            int ct = 0;//路径长度超过ans的路径个数
            MS(cf , 0);//把各点经过的次数清零 
            if(ans >= mx) return 1;//如果当前答案大于等于最长路径 肯定行
            rep(i , 1, k) { //寻找哪些路径长度大于当前ans,找到,求是否存在一条比较长的公共边使得减去后mx小于等于ans 
                if(dis[i] > ans){
                    ct++;
                    cf[l[i]]++;
                    cf[r[i]]++;
                    cf[lca[i]] -= 2;
                }
            }
            dfs2(1 , 0); //计算在这些超过ans长度的路径中,每条边被经过的次数 
            return dfsCheck(1 , 0, ct , mx - ans); 
        } 
        
        int main(){
            int u,v,w;
            while(sc2(n,k) != EOF){
                Init();
                //建树 
                rep(i , 1, n-1){
                    sc2(u,v);
                    sc(w);
                    addEdge(u,v,w);
                    addEdge(v,u,w);
                    me = max(me , w); 
                }
        
                dfs(1 , 0 , 1) ;//初始化LCA 
                init(); //初始化LCA
                
                //读取路径 
                rep(i , 1, k){
                    sc2(u , v);
                    l[i] = u;
                    r[i] = v; 
                    lca[i] = LCA(u,v);
                    dis[i] = dist[u] + dist[v] - 2*dist[lca[i]] ;//求该路径的长度 
                    mx = max(mx , dis[i]);//求出最长路径 
                }
                
                int lo = mx - me;
                int hi = mx + 1;
                while(lo <= hi){
                    int mid = (lo + hi) / 2;
                    if(judge(mid)){
                        hi = mid - 1;
                        ans = mid;
                    } 
                    else{
                        lo = mid + 1;
                    }
                }
                
                pfd(ans);
            }
            return 0;
        }
        
  • 相关阅读:
    OpenSSH服务——密钥登录
    进程管理
    磁盘管理
    文件系统
    shell命令手册
    第一次常用命令手册
    远程连接mobaxterm安装使用
    Linux 系统CentOS 7 64 位安装
    PythonI/O进阶学习笔记_11.python的多进程
    PythonI/O进阶学习笔记_10.python的多线程
  • 原文地址:https://www.cnblogs.com/czsharecode/p/10933185.html
Copyright © 2011-2022 走看看