zoukankan      html  css  js  c++  java
  • 普及分治(那自然是未完成版)


    概览

    序列 时间轴
    普通分治 分治 点分治 CDQ分治
    将过程建树 线段树 点分树 时间线段树

    由于是普及分治, 没有数据结构(线段树、点分树和时间线段树)。
    14年候选队的徐寅展写了时间线段树。

    序列分治

    用于计算序列上的问题


    n 个数排序

    用快速排序, 复杂度 (T(n) = 2T(dfrac{n}{2}) + O(n) = O(n log n))


    经典模型

    给定一个长度为 n 的序列和区间 [l,r] 的贡献计算方式 f(l,r), 求所有区间的贡献和。


    例题1
    给定长度为 (n) 的序列, 求逆序对数。
    solution
    给区间 ([l,r]) 规定一个 (mid in [l,r]), 那么 ([l,r]) 的一对逆序对 ((a,b)) 可以分为 (a in [l,mid], ; b in [mid+1,r])(a、b)(mid) 同一侧两类, 用分治计算, 有个经典算法是在归并排序的过程中顺便计算, 时间复杂度为 (O(n log n))


    例题2
    给定长度为 (n) 的序列, 区间的贡献为区间最大值乘以区间长度, 求所有区间的贡献和。
    solution
    假设区间 ([l,r]) 的最大值的位置为 (p), 则这个区间的所有子区间可以分为包括 (p) 和不包括 (p) 两种。

    [sum_{i=l}^p sum_{j=p}^r (j-i+1) = sum_{i=l}^p Big[ (1-i)(r-p+1) + sum_{j=1}^r j - sum_{j=1}^{p-1} j Big] ]

    反正化简完可以 (O(1)) 算, 在此不再赘述。
    按照区间最大值分治, 复杂度为 (O(n))


    例题3
    给定长度为 (n) 的序列, 区间的贡献为区间最大值乘以区间最小值乘以区间长度, 求所有区间的贡献和。
    solution
    按中点分治, 计算区间 ([l,r]) 的经过 (mid) 的子区间的贡献和。
    可以做到 (O(n log n)), 原题是 bzoj 3745 , 具体做法可以随便上网上找篇博客, 比如这篇, 或者这篇
    似乎还有 (CDQ) 分治的做法。


    树分治

    序列分治在树上的拓展 (maybe) 。
    用于解决树上简单路径的统计问题。


    这部分的参考资料

    1.《算法竞赛进阶指南》
    2
    3
    4
    5
    6


    点分治

    POJ1741

    给树指定一个根节点 (rt), 则这棵树里的简单路径分为两类 :

    1. 路径上的点包含 (rt)
    2. 路径上的不点包含 (rt)

    第二类路径可以递归处理, 一个核心问题是第一类路径的计算。
    这就是点分治的最重要的思想了, 然而点分治的难度不止如此。

    一般来说, 对于第一类路径的计算要有一个固定的复杂度, 剩下的影响复杂度的因素就是分治的递归层数。若每次选择树的重心为根节点递归, 则总的递归层数不超过 (O(log 整棵树的节点个数))

    有一篇 值得一读的博客, 感兴趣的可以看看。

    本题的几种做法
    不同做法的区别是第一类路径的计算方式。

    首先是直接统计的方法。
    逐个扫描不同子树内的节点, 在数据结构里查询, 扫描完后将整个子树内的有关数据插入到数据结构里。

    难写, 不写了。

    一种优化双指针的算法。我从《进阶指南》上看到它。
    d[x]x 到根节点的距离, b[x] 记录 x 属于根节点的哪颗子树。
    把树中所有点放进数组 a , 按照 d 值排序。
    用双指针 lr 分别从前端和后端开始扫描 a 数组, 过程中要始终满足 d[a[l]] + d[a[r]] <= K, 显然随着 l 的增加, r 的位置是单调的, 整个过程复杂度较低。
    扫描的同时用数组 cnt[s] 维护在 l+1r 之间满足 b[a[i]] = s 的位置 i 的个数。
    于是, 路径的一端为 a[l] 时, 满足题目要求的路径另一端的个数就是 r-l-cnt[b[a[l]]]

    这个算法对于普通的双指针优化, 达到了去重和去不合法的效果, 很妙而且优美。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 10006;
    
    int n,k,s[N],Ans;
    int ct, hd[N], nt[N<<1], vr[N<<1], vl[N<<1];
    bool w[N], v[N];
    int ans, pos;
    int tot, a[N], b[N], d[N], cnt[N];
    
    void dfs_find(int S,int x) {
        v[x] = 1;
        s[x] = 1;
        int max_part = 0;
        for(int i=hd[x];i;i=nt[i]) {
            int y=vr[i];
            if(w[y] || v[y]) continue;
            dfs_find(S,y);
            s[x] += s[y];
            max_part = max(max_part, s[y]);
        }
        max_part = max(max_part, S - s[x]);
        if(ans > max_part) {
            ans = max_part;
            pos = x;
        }
    }
    
    void dfs(int x) {
        v[x] = 1;
        for(int i=hd[x];i;i=nt[i]) {
            int y = vr[i], z = vl[i];
            if(w[y] || v[y]) continue;
            ++cnt[b[a[++tot]=y]=b[x]];
            d[y] = d[x] + z;
            dfs(y);
        }
    }
    
    bool cmp(int i,int j) {
        return d[i] < d[j];
    }
    
    void work(int S, int x) {
        memset(v,0,sizeof v);
        ans = S;
        dfs_find(S, x);
        memset(v,0,sizeof v);
        memset(d,0,sizeof d);
        memset(cnt, 0, sizeof cnt);
        w[a[tot=1] = b[pos] = pos] = 1;
        ++cnt[pos];
        for(int i=hd[pos];i;i=nt[i]) {
            int y=vr[i], z=vl[i];
            if(w[y] || v[y]) continue;
            ++cnt[a[++tot]=b[y]=y];
            d[y] = z;
            dfs(y);
        }
        sort(a+1,a+1+tot,cmp);
        int l=1, r=tot;
        --cnt[b[a[l]]];
        while(l<r) {
            while(d[a[l]]+d[a[r]] > k) --cnt[b[a[r--]]];
            Ans += r-l-cnt[b[a[l]]];
            --cnt[b[a[++l]]];
        }
        int now = pos;
        for(int i=hd[now];i;i=nt[i]) {
            int y=vr[i];
            if(w[y]) continue;
            work(s[y], y);
        }
    }
    
    void ad(int x,int y,int z) {
        vr[++ct] = y;
        vl[ct] = z;
        nt[ct] = hd[x];
        hd[x] = ct;
    }
    
    void Tree() {
        ct = 0;
        memset(hd,0,sizeof hd);
        for(int i=1;i<n;++i) {
            int x,y,z;
            scanf("%d%d%d", &x,&y,&z);
            ++x; ++y;
            ad(x,y,z);
            ad(y,x,z);
        }
        Ans = 0;
        memset(w, 0, sizeof w);
        work(n, 1);
        cout << Ans << '
    ';
    }
    
    int main() {
        while(cin>>n>>k&&n&&k) Tree();
        return 0;
    }
    

    双指针+容斥的做法。由 PinkRabbit 提出。
    首先去掉简单路径的限制统计到根节点距离和 (le K) 的点对数量, 然后用减法原理去掉非简单路径的部分, 具体地, 在根节点的每颗子树内都用一次上述统计方法。

    这个算法也很优美。

    #include<bits/stdc++.h>
    using namespace std;
    
    int main() {
    
        return 0;
    }
    

    [IOI2011]Race
    点分治, 问题转化为计算有根树过根节点的路径。
    开个桶, 问题不大。
    TLE 成 80, 自闭了orz

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 200003;
    
    int n,k,s[N],Ans;
    int ct, hd[N], nt[N<<1], vr[N<<1], vl[N<<1];
    bool v[N], w[N];
    int ans, pos;
    int d[N], num[N], min_num[1000001], dl;
    
    void dfs_find(int S,int x) {
      v[x] = 1;
      s[x] = 1;
      int max_part = 0;
      for(int i=hd[x];i;i=nt[i]) {
        int y = vr[i];
        if(w[y] || v[y]) continue;
        dfs_find(S,y);
        s[x] += s[y];
        max_part = max(max_part, s[y]);
      }
      max_part = max(max_part, S-s[x]);
      if(ans > max_part) {
        ans = max_part;
        pos = x;
      }
    }
    
    void dfs(int x, int D, int Num) {
      if(D > k) return;
      v[x] = 1;
      d[++dl] = D, num[dl] = Num;
      for(int i=hd[x];i;i=nt[i]) {
        int y = vr[i];
        if(w[y] || v[y]) continue;
        dfs(y, D+vl[i], Num+1);
      }
    }
    
    void work(int S,int x) {
      memset(v,0,sizeof v);
      ans = S;
      dfs_find(S, x);
      w[pos] = 1;
      
      memset(v,0,sizeof v);
      dl=0;
      min_num[0] = 0;
      for(int i=hd[pos];i;i=nt[i]) {
      	int y = vr[i], z = vl[i];
      	if(w[y] || v[y]) continue;
      		int pdl = dl;
      		dfs(y, z, 1);
      		for(int j=pdl+1;j<=dl;++j) Ans = min(Ans, min_num[k-d[j]]+num[j]);
      			for(int j=pdl+1;j<=dl;++j) min_num[d[j]] = min(min_num[d[j]], num[j]);
      }
      for(int i=1;i<=dl;++i) min_num[d[i]] = 1e9;
      
      for(int i=hd[pos];i;i=nt[i]) {
        int y = vr[i];
        if(w[y]) continue;
        work(s[y], y);
      }
    }
    
    void ad(int x,int y,int z) {
      vr[++ct] = y;
      vl[ct] = z;
      nt[ct] = hd[x];
      hd[x] = ct;
    }
    
    int main() {
      cin  >> n >> k;
      Ans = n+1;
      for(int i=1;i<n;++i) {
        int x,y,z;
        scanf("%d%d%d", &x,&y,&z);
        ++x; ++y;
        ad(x,y,z);
        ad(y,x,z);
      }
      
      memset(min_num, 0x63, sizeof min_num);
      min_num[0] = 0;
      work(n, 1);
      
      cout << (Ans==n+1 ? -1 : Ans);
      return 0;
    }
    

    边分治

    CDQ 分治(基本)

    用 CDQ 写单点加区间查询

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 500055;
    inline int read()
    {
      register int X=0;
      register char ch=0,w=0;
      while(ch<48||ch>57)ch=getchar(),w|=(ch=='-');
      while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
      return w?-X:X;
    }
    
    int n,m, a[N];
    struct node{
    	int x,y,type;
    	// 1是修改, 2是减法, 3是加法
    	// x 是位置
    	// y 权            答案记在哪
    } b[N*3+1], s[N*3+1];
    int tn;
    int Ans[N], q;
    
    void CDQ(int l, int r) {
    	if(l==r) return;
    	int mid = (l+r)>>1;
    	CDQ(l,mid); CDQ(mid+1,r);
    	int p = mid+1, sum = 0, hd = l-1;
    	for(int i=l;i<=mid;++i) {
    		while(p<=r && b[p].x < b[i].x) {
    			s[++hd] = b[p];
    			if(b[p].type==2) Ans[b[p].y] -= sum;
    			if(b[p].type==3) Ans[b[p].y] += sum;
    			++p;
    		}
    		if(b[i].type==1) sum += b[i].y;
    		s[++hd] = b[i];
    	}
    	while(p<=r) {
    		s[++hd] = b[p];
    		if(b[p].type==2) Ans[b[p].y] -= sum;
    		if(b[p].type==3) Ans[b[p].y] += sum;
    		++p;
    	}
    	memcpy(b+l,s+l,sizeof(node)*(r-l+1));
    }
    
    int main() {
    	
    	scanf("%d%d", &n,&m);
    	for(int i=1;i<=n;++i) {
    		b[++tn] = (node){i,read(),1};
    	}
    	for(int i=1, op;i<=m;++i) {
    		op = read();
    		if(op==1) {
    			op = read();
    			b[++tn] = (node){op,read(),1};
    		} else {
    			++q;
    			
    			b[++tn] = (node){read()-1, q, 2};
    			b[++tn] = (node){read(), q, 3};
    		}
    	}
    	CDQ(1,tn);
    	for(int i=1;i<=q;++i) cout << Ans[i] << '
    ';
    	return 0;
    }
    

    三维偏序问题

    陌上花开
    先按第一维排序, 去重, 后面的不会对前面的产生贡献。
    (CDQ), 要解决的就是修改都在询问前头的二位偏序。
    由于前一半的第一维一定都小于后一半的第一维, 所以把前一半标记, 再按照第二维排序后, 套个树状数组即可解决问题, 这样的正确性可以保证。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 100003;
    
    int n,m,k;
    struct node{
    	int x,y,z,w,op,ans;
    } a[N], b[N];
    
    int d[N];
    
    int t[N*2+1];
    void ad(int x,int v) {
    	for(;x<=k;x+=x&(-x)) t[x] += v;
    }
    void cl(int x,int v) {
    	for(;x<=k;x+=x&(-x)) t[x] -= v;
    }
    int ques(int x) {int res=0;
    	for(;x;x-=x&(-x)) res += t[x]; return res;
    }
    
    bool cmp2(node s1, node s2) {
    	return s1.y==s2.y ? (s1.x==s2.x ? s1.z<s2.z : s1.x<s2.x) : s1.y<s2.y;
    }
    
    void cdq(int l,int r) {
    	if(l==r) return;
    	int mid = (l+r)>>1;
    	cdq(l,mid); cdq(mid+1,r);
    	for(int i=l;i<=mid;++i) b[i].op=1;
    	for(int i=mid+1;i<=r;++i) b[i].op=2;
    	sort(b+l,b+r+1,cmp2);
    	for(int i=l;i<=r;++i) {
    		if(b[i].op==1) ad(b[i].z, b[i].w);
    		else b[i].ans += ques(b[i].z);
    	}
    	for(int i=l;i<=r;++i) if(b[i].op==1) cl(b[i].z,b[i].w);
    }
    
    bool cmp(node s1, node s2) {
    	return s1.x==s2.x ? (s1.y==s2.y ? s1.z<s2.z : s1.y<s2.y) : s1.x<s2.x;
    }
    
    int main() {
    	
    	cin >> n >> k;
    	for(int i=1;i<=n;++i) {
    		scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);
    		a[i].w = 1;
    	}
    	sort(a+1, a+1+n, cmp);
    	for(int i=1;i<=n;++i) {
    		if(a[i].x==a[i+1].x && a[i].y==a[i+1].y && a[i].z==a[i+1].z) a[i+1].w += a[i].w;
    		else b[++m] = a[i];
    	}
    	
    	cdq(1,m);
    	for(int i=1;i<=m;++i) d[b[i].ans+b[i].w-1] += b[i].w;
    	for(int i=0;i<n;++i) cout << d[i] << '
    ';
    	
    	return 0;
    }
    

    例题

    整体二分

    参考文献:

    1
    2
    3
    4
    5
    6

  • 相关阅读:
    对网页图片的增删改管理
    还没搞完的排序(后期更新)
    web实现图片动态
    C++11 笔记
    如何解决刷新系统桌面响应速度很慢的问题
    CGrowableArray解析 _ DXUT容器
    测试...外部指针访问private
    CustomUI Direct3D9_Sample
    缺少.lib文件导致的Link2019 解决方案汇总
    在DirectX9中使用DXUT定制按钮来控制模型旋转的问题
  • 原文地址:https://www.cnblogs.com/tztqwq/p/13523243.html
Copyright © 2011-2022 走看看