zoukankan      html  css  js  c++  java
  • 洛谷P4135 作诗

    题意:N个数,M组询问,每次问[l,r]中有多少个数出现正偶数次。(强制在线)

    强制在线,不能用莫队、树状数组等优雅算法,考虑暴力分块。

    设分块后,f[i] [j] 表示第i块到第j块的答案。

    于是,对于每次询问,设p为l所在块的编号,q为r所在块的编号,先查询l到r之间整块的答案,即f[p] [q]。

    之后对于两边的不完整的块,暴力扫每个数,记录出现次数,与这个数在第p块和第q块之间出现的次数比较

    • 若这个数在两边的不完整的块和中间的整块中出现次数均为奇数,++ans
    • 若这个数在两边的不完整的块出现偶数次,在中间的整块中出现次数为0,++ans
    • 若这个数在两边的不完整的块出现奇数次,在中间的整块中出现次数为偶数且不为0,--ans

    于是,问题关键在于如何快速求出某个数在任意两块间的出现次数。

    记分块的数量为T,每块的长度len = n / T。序列长度n、询问次数m同阶。

    解法一:

    与 洛谷P4168 [Violet]蒲公英 类似,对每种数开一个vector,存下这个数在原序列中出现的位置,查询某个数在[L,R]出现次数,只需二分查找<=R的最大下标和>=L的最小下标作差+1.

    如 1 2 2 1 2 3 2 这组数,查询2在[3,6]之间出现的次数。vector[2]={2,3,5,7},即2在原序列中出现的位置。二分查找<=6的最大数为5,在vector中下标为2(vector下标从0开始),之后再二分查找>=3的最小数为3,在vector中的下标为1,两个下标作差+1 = 2,即查询结果为2。

    预处理时,从每个块的起点到n进行统计,复杂度(O(T*n))

    查询时,对两边不完整块的每个数需要二分查找,每次查找为(logn) ,共(n/T) 个数。

    总时间复杂度(O(nT+m(n/T*logn))) ,空间为(T^2) ,取(T=sqrt{n*logn}) ,此时总复杂度(O(nsqrt{n*logn}))

    注意:

    • 这里T如果取(sqrt n) 会导致效率低下,(sqrt{n*logn}) 约为(sqrt n) 的三到四倍。
    • vector常数很大,再加上二分用STL中的lower_bound和upper_bound,常数上天。如果分块数量正确,开了O2之后勉强能过。
    • 可以手写二分,用数组代替vector,不开O2就能过。如果再加上一些卡常技巧(快读、inline、register等)跑的很快。

    Code

    #pragma GCC optimize(2)
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <cctype>
    #include <vector>
    #include <algorithm>
    using namespace std;
    const int N = 1e5 + 5, T = 1280;
    vector<int> pos[N];
    int n, c, m, len, tim, Stack[N], v[N], cnt[N], vis[N], block[N], bl[T], br[T], f[T][T];
    inline void read(int &tmp) {
        register char c = getchar();
        for(tmp = 0; !isdigit(c); c = getchar());
        for(; isdigit(c); tmp = tmp * 10 + c - 48, c = getchar());
    }
    void init() {
        read(n);
        read(c);
        read(m);
        len = ceil(n * 1.0 / (sqrt(n * log(n))));
        for(int i = 1; i <= n; ++i) {
        read(v[i]);
            pos[v[i]].push_back(i);
            block[i] = (i - 1) / len + 1;
        }
        for(int i = 1; i <= block[n]; ++i) {
        bl[i] = (i - 1) * len + 1;
            br[i] = i * len;
        }
        br[block[n]] = n;
        for (int i = 1, res; i <= block[n]; ++i) {
        res = 0;
        for(int j = bl[i]; j <= n; ++j) cnt[v[j]] = 0;
            for (int j = bl[i]; j <= n; ++j) {
                ++cnt[v[j]];
                if ((cnt[v[j]] & 1) && (cnt[v[j]] > 1)) --res;
                else if (!(cnt[v[j]] & 1)) ++res;
                f[i][block[j]] = res;
            }
        }
    }
    inline int get(int x, int L, int R) {
        return upper_bound(pos[x].begin(), pos[x].end(), R) - lower_bound(pos[x].begin(), pos[x].end(), L);
    }
    int query(int L, int R) {
        int p = block[L], q = block[R], tp = 0, res = 0;
        if(q - p < 2) {//同块或相邻块
            for(int i = L; i <= R; ++i) {//暴力扫
                if(vis[v[i]] != tim) {//第一次扫到的标记
                    vis[v[i]] = tim;
                    cnt[v[i]] = 1;
                } else {
                    ++cnt[v[i]];
                    if(cnt[v[i]] & 1) --res;
                    else ++res;
                }
            }
            return res;
        }
        res = f[p + 1][q - 1];//两块间的答案
        for(int i = L; i <= br[p]; ++i) {//左边角块
            if(vis[v[i]] != tim) {
                vis[v[i]] = tim;
                cnt[v[i]] = 1;
                Stack[++tp] = v[i];
            } else ++cnt[v[i]];
        }
        for(int i = bl[q]; i <= R; ++i) {//右边角块
            if(vis[v[i]] != tim) {
                vis[v[i]] = tim;
                cnt[v[i]] = 1;
                Stack[++tp] = v[i];
            } else ++cnt[v[i]];
        }
        while(tp) {
            int t = Stack[tp--], k = get(t, bl[p + 1], br[q - 1]);
            if(!k) {
                if(!(cnt[t] & 1)) ++res;
            } else if((k & 1) && (cnt[t] & 1)) ++res;
            else if(!(k & 1) && (cnt[t] & 1)) --res;
        }
        return res;
    }
    int main() {
        init();
        int res = 0;
        for(int i = 1, l, r; i <= m; ++i) {
            ++tim;
            read(l);
            read(r);
            l = (l + res) % n + 1;
            r = (r + res) % n + 1;
            if(l > r) swap(l, r);
            printf("%d
    ", res = query(l, r));
        }
        return 0;
    }
    

    解法二:

    利用前缀和思想,设g[i] [j]表示从序列开始到第i块中,数字j的出现次数。则查询数字x在第p+1块到第q-1块的出现次数,为g[q-1] [x] - g[p] [x]。

    实际写的时候,可以把g数组定义为第i块到序列末尾中数字j的出现次数,即后缀和,方便在同一个循环中处理。查询时类似于前缀和。

    这样每次查询为O(1),显然,总复杂度为(O(nsqrt n)) ,空间为((Tn))

    理论复杂度比解法一快,实际上,当T取(sqrt n) 时,不开O2有两个点会TLE(可能是我写的太丑了QAQ)。然鹅,T取较大值时,耗时更短,但空间会炸(要开T*n的二维数组...)

    Code

    #pragma GCC optimize(2)
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <cctype>
    using namespace std;
    const int N = 1e5 + 5, T = 350;
    int n, c, m, len, Stack[N], v[N], cnt[N], block[N], bl[T], br[T], f[T][T], g[T][N];
    inline void read(int &tmp) {
        register char c = getchar();
        for(tmp = 0; !isdigit(c); c = getchar());
        for(; isdigit(c); tmp = tmp * 10 + c - 48, c = getchar());
    }
    void init() {
        read(n);
        read(c);
        read(m);
        len = sqrt(n);
        for(register int i = 1; i <= n; ++i) {
            read(v[i]);
            block[i] = (i - 1) / len + 1;
        }
        for(int i = 1; i <= block[n]; ++i) {
            bl[i] = (i - 1) * len + 1;
            br[i] = i * len;
        }
        br[block[n]] = n;
        for (int i = 1, res; i <= block[n]; ++i) {
            res = 0;
            for (int j = bl[i]; j <= n; ++j) {
                ++g[i][v[j]];
                if ((g[i][v[j]] & 1) && (g[i][v[j]] > 1)) --res;
                else if (!(g[i][v[j]] & 1)) ++res;
                f[i][block[j]] = res;
            }
        }
    }
    int query(int L, int R) {
        int p = block[L], q = block[R];
        register int res = 0, tp = 0, c, num;
        if(q - p < 2) {
            for(int i = L; i <= R; ++i) ++cnt[Stack[++tp] = v[i]];
            while(tp) {
                c = Stack[tp];
                if(cnt[c]) res += (cnt[c] & 1) ^ 1;
                cnt[Stack[tp--]] = 0;
            }
            return res;
        }
        res = f[p + 1][q - 1];
        for(int i = L; i <= br[p]; ++i) ++cnt[Stack[++tp] = v[i]];
        for(int i = bl[q]; i <= R; ++i) ++cnt[Stack[++tp] = v[i]];
        while(tp) {
            c = Stack[tp];
            if(cnt[c]) {
                num = g[p + 1][c] - g[q][c];
                if((num & 1) && (cnt[c] & 1)) ++res;
                else if(!(num & 1) && num && (cnt[c] & 1)) --res;
                else if(!num && !(cnt[c] & 1)) ++res;
            }
            cnt[Stack[tp--]] = 0;
        }
        return res;
    }
    int main() {
        init();
        int res = 0;
        for(register int i = 1, l, r; i <= m; ++i) {
            read(l);
            read(r);
            l = (l + res) % n + 1;
            r = (r + res) % n + 1;
            if(l > r) swap(l, r);
            printf("%d
    ", res = query(l, r));
        }
        return 0;
    }
    
  • 相关阅读:
    AcWing 3302. 表达式求值
    AcWing 828. 模拟栈
    六种风格时间显示
    web2.0常用配色.
    CSS浏览器兼容问题详解
    jQuery Cycle Plugin Beginner Demos
    jQuery插件Clipboard Copy(复制)。
    精通jQuery选择器使用
    jQuery插件右下角弹出信息
    CSS关于box(盒模式)的一系列问题
  • 原文地址:https://www.cnblogs.com/yu-xing/p/10878789.html
Copyright © 2011-2022 走看看