zoukankan      html  css  js  c++  java
  • [学习笔记]多维偏序

    一般情况下,我们比较一个数大小,就是ai>aj即可,

    而在上升子序列中,当i>j并且ai>aj的时候,才可以认为i这位的数大于j这位的数。

    这就是一个二维偏序。

    类似的,有n个数,每个数m个属性,一个数比另一个数大,当且仅当这个数的所有属性都大于另一个数。

    这就是一个m维偏序。

    对于三维偏序,可以用cdq分治、排序、树状数组处理。

    luogu P3810

    Description:

    有 n 个元素,第 i 个元素有 ai 、bi 、ci 三个属性,设 f(i) 表示满足 ajai 且bjbi 且cjci 的 j 的数量。

    对于d[0,n) ,求 f(i) = d的数量

    Solution:

    三维偏序的模板题。

    我们先按照a从小到大排序,

    然后cdq分治。

    先递归到两边,

    回溯到这一层之后,把左儿子的所有的数按照b排序,右儿子的所有数也按照b排序。

    这样,左儿子的数之间虽然a不一定递增,但是因为开始按照a排序,所以左边所有的数的a一定都小于右边的数。

    b排好序之后,

    两个指针j,i分别从1~mid,mid+1~r 即左右儿子区间的起止点开始走,,

    对于右边的一个数i,当j的数的b值不大于i的b值时,不断向后走j,并且把这些数的c值放进一个权值树状数组里,

    当j的b值大于i之后,当前所有左儿子里面,b小于i这个数的数的c属性都放进树状数组里了。

    只有放进去的这些数才可能来更新f值。

    在i向后走之前,f[a[i].ans]+=query(a[i].z),满足第三个条件的数也找到了。

    就把所有当前这个层里面,符合条件的数都找出来了。

    最后,把树状数组加上的 1都消去。

    由于cdq分治,会把i之前的所有的数都分成logn个区间,更新完f[i]了。

    大家可以手动画图,或者模拟一下。

    复杂度:nlogn^2

    那么,这个算法是怎么样实现三维偏序的处理呢?

    1.对于a,开始直接排序,并且,每次是先递归左右儿子,再处理这一层,

    所以保证一个数pi前面的所有的数,不论之后怎么换位置,都不会到i的后面。

    这就利用位置保证了所有可能更新f[i]的数对于a都是合法的。

    2.对于b,我们每次回溯的时候,按照b排了一个序,

    对于左子区间对右子区间的影响,通过指针,把b小于等于i的数的所有数的c放进了树状数组里。

    这样,i前面的logn个区间,会把所有b小于i的b的数都考虑一遍的。也合法。

    3.对于c,直接通过树状数组前缀和,一步就求出来了当前合法的所有数了。

    相当于一个筛,留下a合法的,留下b合法的,最后能被c留下的,就是所有合法的了。

    注意因为是小于等于号,所以我们先把所有的完全相同的数合并成一个数,统计的时候,一个数也大于等于自己。再加上就好了。

    就是细节问题。

    Code:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=100000+10;
    int n,m;
    int f[N];
    struct node{
        int x,y,z;
        int ans;
        int w;
    }a[N],b[N];
    int pp;
    bool cmpx(node p,node q){
        if(q.x==p.x){
            if(p.y==q.y) return p.z<q.z;
            return p.y<q.y;
        }
        return p.x<q.x;
    }
    bool cmpy(node p,node q){
        if(q.y==p.y){
            return p.z<q.z;
        }
        return p.y<q.y;
    }
    struct ta{
        int g[2*N];
        void add(int x,int k){
            for(;x<=pp;x+=x&(-x)) g[x]+=k;
        }
        int ask(int x){
            int ret=0;
            for(;x;x-=x&(-x)) ret+=g[x];return ret;
        }
    }t;
    
    void cdq(int l,int r){
        //cout<<l<<" and "<<r<<endl;
        if(l==r) return;
        int mid=(l+r)>>1;
        cdq(l,mid);cdq(mid+1,r);
        sort(a+l,a+mid+1,cmpy);
        sort(a+mid+1,a+r+1,cmpy);
        //cout<<" after sort "<<l<<" || "<<r<<endl;
        int i=mid+1,j=l;
        for(;i<=r;i++){
            while(a[j].y<=a[i].y&&j<=mid){
                t.add(a[j].z,a[j].w),j++;
            }
            a[i].ans+=t.ask(a[i].z);
        }
        for(i=l;i<j;i++){
            t.add(a[i].z,-a[i].w);
        }
    }
    int ans[N];
    int main()
    {    
        int m;
        scanf("%d%d",&m,&pp);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].z);
        }
        sort(b+1,b+m+1,cmpx);
        int c=0;
        for(int i=1;i<=m;i++){
            c++;
            if(b[i].x!=b[i+1].x||b[i].y!=b[i+1].y||b[i].z!=b[i+1].z){
                a[++n]=b[i],a[n].w=c;c=0;
            }
        }
        cdq(1,n);
        for(int i=1;i<=n;i++){
            ans[a[i].ans+a[i].w-1]+=a[i].w;
        }
        for(int i=0;i<m;i++){
            printf("%d
    ",ans[i]);
        }
        return 0;
    }
    三维偏序

    要是维数再多了呢??

    hihocoder 1236 Scores

    Decripiton:

    给出N个人的5个科目分数,给出q个询问~~,每次给你5个科目的具体分数。求一共有多少个人对应的5个科目都小于等于你的科目分数~~

    强制在线。

    n,q<=50000

    Solution:

    这就是5维偏序了。

    显然cdq分治不容易解决了。难以巧妙处理5维。

    就考虑比较暴力的思路:

    把成绩5种分成5组,每一组从小到大按成绩排序。

    每次二分出id,id的成绩恰好位于成绩查询边界。

    这样可以知道该成绩是有几个人不满足。

    但是,由于有5个,所以必须知道都是谁。。。。

    用一个bitset<50001>s[5][50000]表示,第i维,前j个人满足不合格的情况下,都是谁(s[i][j][k]=1表示,i维,编号是k的人,成绩比倒数第j名可能还差。)

    然后预处理出bitset(每次j加一,就把j+1的人或进去就行了),查询的时候,二分id,之后取出这5个bitset,&一下就知道最后剩谁了,统计1的个数。

    但是bitset还是太大了,不是MLE,就是TLE。

    所以,考虑分块!??!

    bitset<50001>s[5][250]表示,第i维,前j块不合格,都是谁

    预处理比较容易,一个块一个块内暴力处理,最后从前到后相邻的块一个前缀或就可以了

    查询的时候,

    二分出来一个id,在块k里,

    就找到k,把k-1块的不合格人都找出来,之后剩下的暴力加进去就可以啦

    复杂度:O(5 * q * sqrtn)(不算预处理)

    因为这个是n<=50000,所以分块卡不掉。

    这种数据范围,多维偏序都可以类似扩展开来。

    本质上还是一个筛。不过用了bitset和分块优化。

    总结:

    感觉多维偏序在生活中还是比较常见的,

    比方说,你期末考试之后,想看看完虐多少个人?(每一科都比TA高)

    就是多维偏序了。

  • 相关阅读:
    主从热备+负载均衡(LVS + keepalived)
    这12行代码分分钟让你电脑崩溃手机重启
    Linux 下虚拟机——Virtual Box
    软件著作权登记证书申请攻略
    ecshop整合discuz教程完美教程
    NetHogs——Linux下按进程实时统计网络带宽利用率
    深入研究CSS
    SSH远程会话管理工具
    nginx防止SQL注入规则
    mysql完美增量备份脚本
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9426812.html
Copyright © 2011-2022 走看看