• 初探算法维度问题


    先看一道基础题 HRBUST 2224 

    给定 n 个数组成的数组,求其逆序对的总数。

    逆序对定义为,存在 (i, j) 满足 i < j 且 A[i] > A[j] 的二元组的数目。

    接下来的一行,包含 n 个数(2 <= n <= 100000),依次表示 A[i](A[i] <= 10^9)。

    简单的逆序对问题,很显然可以用树状数组或者归并排序解决,但是考虑到A的范围,常规的树状数组会采用离散化,tree存储前面比她小的数来解决。

    这当然是没有问题的,但是这个题事实上是两个维度(pos,t),pos是一个显而易见的,t其实也是一个维度,也就是出现的顺序,逆序对事实上是要满足(pos1 < pos2 && t1 > t2)的对数,将问题转化为这样的二维来看,pos和t的地位就等同了,我们既然可以用树状数组求pos的前缀来计算逆序数对,当然也可以用t的前缀来计算逆序数对,在常规做法中,t从前向后遍历的时候是保证有序的,所以直接操作即可,当我们用树状数组来维护t的时候,我们就要事先对pos进行排序,此时所有比t小的数都是正常的,我们只要用t减去这些正常的就是这个数的逆序数对。

    #include <map>
    #include <set>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <vector>
    #include <string>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <sstream>
    #include <iostream>
    #include <algorithm>
    #include <functional>
    using namespace std;
    const int MAXBUF=10000;char buf[MAXBUF],*ps=buf,*pe=buf+1;
    inline bool isdigit(const char& n) {return (n>='0'&&n<='9');}
    inline void rnext(){if(++ps==pe)pe=(ps=buf)+fread(buf,sizeof(char),sizeof(buf)/sizeof(char),stdin);}
    template <class T> inline bool in(T &ans){
    #ifdef VSCode
    ans=0;T f=1;register char c;
    do{c=getchar();if ('-'==c)f=-1;}while(!isdigit(c)&&c!=EOF);
    if(c==EOF)return false;do{ans=(ans<<1)+(ans<<3)+c-48;
    c=getchar();}while(isdigit(c)&&c!=EOF);ans*=f;return true;
    #endif
    #ifndef VSCode 
    ans =0;T f=1;if(ps==pe)return false;do{rnext();if('-'==*ps)f=-1;} 
    while(!isdigit(*ps)&&ps!=pe);if(ps==pe)return false;do{ans=(ans<<1)+(ans<<3)+*ps-48;
    rnext();}while(isdigit(*ps)&&ps!=pe);ans*=f;return true;
    #endif
    }const int MAXOUT=10000;   //*(int(*)[10])p
    char bufout[MAXOUT], outtmp[50],*pout = bufout, *pend = bufout+MAXOUT;
    inline void write(){fwrite(bufout,sizeof(char),pout-bufout,stdout);pout = bufout;}
    inline void out_char(char c){*(pout++)=c;if(pout==pend)write();}
    inline void out_str(char *s){while(*s){*(pout++)=*(s++);if(pout==pend)write();}}
    template <class T>inline void out_int(T x) {if(!x){out_char('0');return;}
    if(x<0)x=-x,out_char('-');int len=0;while(x){outtmp[len++]=x%10+48;x/=10;}outtmp[len]=0;
    for(int i=0,j=len-1;i<j;i++,j--) swap(outtmp[i],outtmp[j]);out_str(outtmp);}
    template<typename T, typename... T2>
    inline int in(T& value, T2&... value2) { in(value); return in(value2...); }
    #define For(i, x, y) for(int i=x;i<=y;i++)  
    #define _For(i, x, y) for(int i=x;i>=y;i--)
    #define Mem(f, x) memset(f,x,sizeof(f))  
    #define Sca(x) scanf("%d", &x)
    #define Scl(x) scanf("%lld",&x);  
    #define Pri(x) printf("%d
    ", x)
    #define Prl(x) printf("%lld
    ",x);  
    #define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
    #define LL long long
    #define ULL unsigned long long  
    #define mp make_pair
    #define PII pair<int,int>
    #define PIL pair<int,long long>
    #define PLL pair<long long,long long>
    #define pb push_back
    #define fi first
    #define se second 
    #define Vec Point
    typedef vector<int> VI;
    const double eps = 1e-9;
    const int maxn = 1e6 + 10;
    const int INF = 0x3f3f3f3f;
    const int mod = 1e9 + 7; 
    int N,M,tmp,K; 
    PLL a[maxn];
    LL tree[maxn];
    int lowbit(int t){
        return t & -t;
    }
    void add(int x){
        while(x <= N){
            tree[x]++;
            x += lowbit(x);
        }
    }
    LL query(int x){
        LL s = 0;
        while(x > 0){
            s += tree[x];
            x -= lowbit(x);
        }
        return s;
    }
    int main()
    {
        in(N);
        For(i,1,N) in(a[i].fi) ,a[i].se = i;
        sort(a + 1,a + 1 + N);
        LL ans = 0;
        For(i,1,N){
            ans += a[i].se - 1 - query(a[i].se);
            add(a[i].se);
        }
        Prl(ans);
        #ifdef VSCode
        write();
        system("pause");
        #endif
        return 0;
    }
    View Code

     上述的问题是一个二维问题,下面来看一个三维的问题 POJ1195

    对一个平面进行m次操作

    1 . 在(x,y)上增加或减少值x

    2.查询(x1,y1) 与 (x2,y2)形成的矩形之间的和

    显然,这又是一道二维树状数组的模板题,但是当x,y的范围给到1e9时,我们就需要进行离散化,当m的范围给到1e5时,我们就会发现离散化也不好使了,4e10的数组范围肯定是开不下的,这时候就要考虑另辟蹊径解决这道问题。

    由上面的问题可以联想到,这依然是一个三维度的问题,(x,y,t)是问题的三个维度。当我们只有两个维度的时候,我们可以用树状数组来降一个维度,用排序默认一个维度,当三个维度时依然是用前缀和的思想来处理时,可以加上用cdq分治来维护第三个出现的维度。当我们用二位树状数组的时候,我们每次查询需要查询四个值来确认答案,当我们离线使用分治的时候,我们需要将四个值加入询问,离线排序之后来确定询问。

    #include <map>
    #include <set>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <vector>
    #include <string>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <sstream>
    #include <iostream>
    #include <algorithm>
    #include <functional>
    using namespace std;
    const int MAXBUF=10000;char buf[MAXBUF],*ps=buf,*pe=buf+1;
    inline bool isdigit(const char& n) {return (n>='0'&&n<='9');}
    inline void rnext(){if(++ps==pe)pe=(ps=buf)+fread(buf,sizeof(char),sizeof(buf)/sizeof(char),stdin);}
    template <class T> inline bool in(T &ans){
    #ifdef VSCode
    ans=0;T f=1;register char c;
    do{c=getchar();if ('-'==c)f=-1;}while(!isdigit(c)&&c!=EOF);
    if(c==EOF)return false;do{ans=(ans<<1)+(ans<<3)+c-48;
    c=getchar();}while(isdigit(c)&&c!=EOF);ans*=f;return true;
    #endif
    #ifndef VSCode 
    ans =0;T f=1;if(ps==pe)return false;do{rnext();if('-'==*ps)f=-1;} 
    while(!isdigit(*ps)&&ps!=pe);if(ps==pe)return false;do{ans=(ans<<1)+(ans<<3)+*ps-48;
    rnext();}while(isdigit(*ps)&&ps!=pe);ans*=f;return true;
    #endif
    }const int MAXOUT=10000;   //*(int(*)[10])p
    char bufout[MAXOUT], outtmp[50],*pout = bufout, *pend = bufout+MAXOUT;
    inline void write(){fwrite(bufout,sizeof(char),pout-bufout,stdout);pout = bufout;}
    inline void out_char(char c){*(pout++)=c;if(pout==pend)write();}
    inline void out_str(char *s){while(*s){*(pout++)=*(s++);if(pout==pend)write();}}
    template <class T>inline void out_int(T x) {if(!x){out_char('0');return;}
    if(x<0)x=-x,out_char('-');int len=0;while(x){outtmp[len++]=x%10+48;x/=10;}outtmp[len]=0;
    for(int i=0,j=len-1;i<j;i++,j--) swap(outtmp[i],outtmp[j]);out_str(outtmp);}
    #define For(i, x, y) for(int i=x;i<=y;i++)  
    #define _For(i, x, y) for(int i=x;i>=y;i--)
    #define Mem(f, x) memset(f,x,sizeof(f))  
    #define Sca(x) scanf("%d", &x)
    #define Scl(x) scanf("%lld",&x);  
    #define Pri(x) printf("%d
    ", x)
    #define Prl(x) printf("%lld
    ",x);  
    #define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
    #define LL long long
    #define ULL unsigned long long  
    #define mp make_pair
    #define PII pair<int,int>
    #define PIL pair<int,long long>
    #define PLL pair<long long,long long>
    #define pb push_back
    #define fi first
    #define se second 
    #define Vec Point
    typedef vector<int> VI;
    const double eps = 1e-9;
    const int maxn = 100110;
    const int maxq = 302000;
    const int INF = 0x3f3f3f3f;
    const int mod = 1e9 + 7; 
    int N,M,tmp,K,op; 
    int tree[maxn];
    struct QUERY{
        int x,y,t,op,w,id;
    }query[maxq];
    inline int lowbit(int t) {return t & -t;}
    void add(int x,int val){
        for(;x <= N;x += lowbit(x)){
            tree[x] += val;
        }
    }
    int Query(int x){
        int s = 0;
        for(;x > 0; x -= lowbit(x)){
            s += tree[x];
        }
        return s;
    }
    bool cmp(QUERY a,QUERY b){
        if(a.x != b.x) return a.x < b.x;
        if(a.y != b.y) return a.y < b.y;
        return a.op < b.op;
    }
    int ans[maxq];
    QUERY temp[maxq];
    VI Q;
    void cdq(int l,int r){
        if(l == r) return;
        int m = (l + r) >> 1;
        int l1 = l,l2 = m + 1;
        For(i,l,r){
            if(query[i].t <= m && query[i].op == 1) add(query[i].y,query[i].w);
            if(query[i].t > m && query[i].op == 2) ans[query[i].id] += query[i].w * Query(query[i].y);
       //     cout << query[i].id << "  " << ans[query[i].id] << endl;
        }
        For(i,l,r){
            if(query[i].t <= m && query[i].op == 1){
                add(query[i].y,-query[i].w);
            }
        }
        For(i,l,r){
            if(query[i].t <= m){
                temp[l1++] = query[i];
            }else{
                temp[l2++] = query[i];
            }
        }
        For(i,l,r){
            query[i] = temp[i];
        }
        cdq(l,m); cdq(m + 1,r);
    }
    int main()
    {
        in(tmp); in(N);
        int cnt = 0;
        int op;
        while(in(op)){
            if(op == 1){
                cnt++;
                in(query[cnt].x),in(query[cnt].y),in(query[cnt].w);
                query[cnt].t = cnt; query[cnt].op = 1;
                query[cnt].x++; query[cnt].y++;
            }else if(op == 2){
                int x1,y1,x2,y2; in(x1);in(y1);in(x2);in(y2); x1++,x2++,y1++,y2++;
                int K = ++ans[0];
                query[++cnt].x = x1 - 1; query[cnt].y = y1 - 1; query[cnt].t = cnt; query[cnt].op = 2,query[cnt].id = K;query[cnt].w = 1;
                query[++cnt].x = x2; query[cnt].y = y2; query[cnt].t = cnt; query[cnt].op = 2,query[cnt].id = K;query[cnt].w = 1;
                query[++cnt].x = x2; query[cnt].y = y1 - 1; query[cnt].t = cnt; query[cnt].op = 2,query[cnt].id = K;query[cnt].w = -1;
                query[++cnt].x = x1 - 1; query[cnt].y = y2; query[cnt].t = cnt; query[cnt].op = 2,query[cnt].id = K;query[cnt].w = -1;
            }else{
                break;
            }
        }
        sort(query + 1,query + 1 + cnt,cmp);
        cdq(1,cnt);
        For(i,1,ans[0]){
            Pri(ans[i]);
        }
        #ifdef VSCode
        write();
        system("pause");
        #endif
        return 0;
    }
    View Code

    由此问题发散下去,当我们遇到一个四维的问题时。

    解决的方法是再加上一个cdq分治维护第四个维度。也就是cdq分治套cdq分治加上树状数组,每个询问加入8个查询。

    解决这些问题的主要方法时观察出题目中给出的维度和隐藏的维度,例如逆序对中的t,然后如果这些维度符合前缀和的思想,也就是所有的操作有且仅有一次对后面的影响,就可以考虑用抽象的将其分离,考虑用降维的手段来解决。

  • 相关阅读:
    6389. 【NOIP2019模拟2019.10.26】小w学图论
    6383. 【NOIP2019模拟2019.10.07】果实摘取
    三分查找求极值
    51Nod 1278 相离的圆
    51 Nod 1092 回文字符串
    关于原根(来自百度百科)
    Hdu 1358 Period
    最大子矩阵和
    51 Nod 1072 威佐夫游戏
    The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online(2018 青岛网络预选赛)
  • 原文地址:https://www.cnblogs.com/Hugh-Locke/p/9500288.html
Copyright © 2020-2023  润新知