莫队入门 HYSBZ - 2038 & CF - 617E & CF - 220B & NBUT - 1457 & SPOJ - DQUERY//LightOJ - 1188 & CF - 86D

HYSBZ - 2038【小Z的袜子】

https://cn.vjudge.net/contest/304248#problem/A

题意

给定 n 个数和 m 次询问,其中 n 个数表示的是“袜子的颜色”,每次询问输出抽到相同颜色袜子的概率(最简分数)。

分析

做主席树做到一题树上莫队,然后懵了,于是回来从普通莫队算法开始补起好了。

莫队算法:基本可以分为三类,普通莫队、树形莫队、带修改莫队。

思路:离线情况下对所有的询问进行一个美妙的 sort(),然后用两个指针l, r(本题是两个,其他的题可能会更多)不断以看似暴力的方式在区间内跳来跳去,最终输出答案。当知道一个区间的信息后,要求出旁边区间的信息(旁边区间由当前区间的一个指针通过 +1/-1 得到),只需要 \(O(1)\) 的时间。

那么什么才是美妙的 sort() 呢?如果以读入的顺序来枚举每个询问,那么可能会出现指针 l、r 跳来跳去的景象,这样的复杂度与暴力并无二致 \(O(n^2)\) 。而莫队算法采用的是分块的思想,对于两个不同查询来说,如果它们的左端点 l 在同一块,那么就按照右端点 r 进行排序;否则还是正常按左端点 l 排序。如果这样做,这样就会得到优化后的时间复杂度:$$O(nblock+nn/block)$$

即:$$O(n*\sqrt{n})$$

值得一提的是,莫队算法的核心和难点在与判断相邻两块可以通过什么样的数学关系进行关联(如何转移)。还有就是先动指针还是先转移千万不能弄乱了。

对于这题来说,分母是 \(n*n-n\) (表示两两袜子之间的随机组合),分子是 \(res-n\),其中 \(res\) = “该区间内每种颜色 i 出现次数 sum[i] 的平方”。

代码

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 5e4+5;

int n, m;
int a[maxn];
int block;         // 块数
ll mark[maxn];     // 标记当前区间内颜色 i 已经出现 mark[i] 次
ll res;            // 当前答案
struct Question {
    int l, r, id;
    bool operator < (const Question &a) const {
        if(l/block == a.l/block) {  // 只有当左端点在同一块中,才按照右端点排序
            return r < a.r;
        }
        return l < a.l;
    }
}Q[maxn];

struct ANS {
    ll a, b;
}ans[maxn];

void init() {
    block = sqrt(n);
    memset(mark, 0, sizeof(mark));
    res = 0;
}

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a%b);
}

void del(int x) {
    res -= mark[a[x]]*mark[a[x]];
    mark[a[x]] --;
    res += mark[a[x]]*mark[a[x]];
}

void add(int x) {
    res -= mark[a[x]]*mark[a[x]];
    mark[a[x]] ++;
    res += mark[a[x]]*mark[a[x]];
}

int main() {
    while(~scanf("%d%d", &n, &m)) {
        init();
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &Q[i].l, &Q[i].r);
            Q[i].id = i;
        }
        sort(Q+1, Q+1+m);
        int l = 1, r = 0;
        for(int i = 1; i <= m; i++) {
            while(l < Q[i].l) {
                del(l);  // 左端当前点为多余点,先删再移。
                l++;
            }
            while(l > Q[i].l) {
                l--;
                add(l);  // 左端有节点未加入,先移再加。
            }
            while(r < Q[i].r) {
                r++;
                add(r);  // 右端有节点未加入,先移再加。
            }
            while(r > Q[i].r) {
                del(r);  // 右端当前点为多余点,先删再移。
                r--;
            }
            ans[Q[i].id].a = res - (r-l+1);
            ans[Q[i].id].b = 1ll*(r-l+1)*(r-l);
        }
        for(int i = 1; i <= m; i++) {
            ll t = gcd(ans[i].a, ans[i].b);
            if(ans[i].a == 0) {
                printf("0/1\n");
            }
            else
                printf("%lld/%lld\n", ans[i].a/t, ans[i].b/t);
        }
    }
    return 0;
}

CodeForces - 617E 【XOR and Favorite Number】

https://cn.vjudge.net/contest/304248#problem/G

题意

给定 n 个数和、m 次询问和定值 k,每次询问区间 [l, r] 中有多少对 [i, j] 满足 \(a^{i}\) ^ \(a^{i+1}\) ^ ...... ^ \(a^{j-1}\) ^ \(a^{j}\) == \(k\) ,即区间异或和等于 k。

分析

预处理出所有前缀异或和\(a[n]\) 表示前 n 个数的异或结果。因为 \(x\) ^ \(x\) == 0 ,所以如果要求区间 [i, j] 上的异或结果,可以用 \(a[j]\) ^ \(a[i-1]\) 得到。因此,我们用 mark数组 记录前缀和出现的次数。在查询的时候,\(a[i]\) ^ \(k\) 就是我们要找的前缀和的大小,因为前面知道如果我们要求区间 [i, j] 上的异或结果,可以用 \(a[j]\) ^ \(a[i-1]\) 得到,所以我们要找之前有多少个前缀,和现在的前缀异或值为k,对应到flag数组去找 \(a[i]\) ^ \(k\) 的个数,并更新答案就行了。

在推出这样的转移方式之后,就可以套进莫队模板了。那推不出来咋办...

代码

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = (1<<20);

int n, m, k;
int a[maxn];    // 直接记录前缀和
int mark[maxn]; // 还是个数
int block;
ll ans[maxn];
ll res;

struct node {
    int l, r, id;
    bool operator < (const node &a) const {
        if(l/block == a.l/block) {
            return r < a.r;
        }
        return l < a.l;
    }
}Q[maxn];

void init() {
    res = 0;
    memset(mark, 0, sizeof(mark));
    block = sqrt(n);
    mark[0] = 1;    // 什么都不取也有个 0
}

void del(int x) {
    mark[a[x]]--;
    res -= 1ll*mark[a[x]^k];
}

void add(int x) {
    res += 1ll*mark[a[x]^k];
    mark[a[x]]++;
}

int main() {
    // fopen("in.txt", "r", stdin);
    // fopen("out.txt", "w", stdout);
    while(~scanf("%d%d%d", &n, &m, &k)) {
        init();
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            a[i] = a[i-1] ^ a[i];
        }
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &Q[i].l, &Q[i].r);
            Q[i].id = i;
        }
        sort(Q+1, Q+1+m);
        int l = 1, r = 0;
        for(int i = 1; i <= m; i++) {
            while(l < Q[i].l) {
                del(l-1);
                l++;
            }
            while(l > Q[i].l) {
                l--;
                add(l-1);
            }
            while(r < Q[i].r) {
                r++;
                add(r);
            }
            while(r > Q[i].r) {
                del(r);
                r--;
            }
            ans[Q[i].id] = res;
        }
        for(int i = 1; i <= m; i++) {
            printf("%lld\n", ans[i]);
        }
    }
    return 0;
}

CodeForces - 220B 【Little Elephant and Array】

https://cn.vjudge.net/contest/304248#problem/E

题意

给定 n 个数和m 次询问,每次询问区间 [l, r] 里有多少数出现次数等于它的值。

分析

还是莫队板子题,不过需要离散化(\(a_{i} \leq 1e9\))。

代码

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;

const int maxn = 1e5+5;

int n, m;
int mark[maxn];
int block;
int ans[maxn];
int res;
int r[maxn];
int cur_a[maxn];

struct A{
    int val, pos;
}a[maxn];

bool cmp(A x, A y) {
    return x.val < y.val;
}

struct node{
    int l, r, id;
    bool operator < (const node &a) const {
        if(l/block == a.l/block) {
            return r < a.r;
        }
        return l < a.l;
    }
}Q[maxn];

void init() {
    block = sqrt(n);
    res = 0;
    memset(mark, 0, sizeof(mark));
}

void del(int x) {
    int y = r[x];
    if(mark[y] == cur_a[x] + 1) {
        res ++;
    }
    else if(mark[y] == cur_a[x]) {
        res --;
    }
    mark[y]--;
}

void add(int x) {
    int y = r[x];
    if(mark[y] == cur_a[x] - 1) {
        res ++;
    }
    else if(mark[y] == cur_a[x]) {
        res --;
    }
    mark[y]++;
}

int main() {
    // fopen("in.txt", "r", stdin);
    // fopen("out.txt", "w", stdout);
    while(~scanf("%d%d", &n, &m)) {
        init();
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i].val);
            a[i].pos = i;
            cur_a[i] = a[i].val;
        }
        sort(a+1, a+1+n, cmp);
        int cnt = 1;
        r[a[1].pos] = 1;
        for(int i = 2; i <= n; i++) {
            if(a[i].val == a[i-1].val) {
                r[a[i].pos] = cnt;
            }
            else {
                r[a[i].pos] = ++cnt;
            }
        }
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &Q[i].l, &Q[i].r);
            Q[i].id = i;
        }
        sort(Q+1, Q+1+m);
        int l = 1, r = 0;
        for(int i = 1; i <= m; i++) {
            while(l < Q[i].l) {
                del(l);
                l++;
            }
            while(l > Q[i].l) {
                l--;
                add(l);
            }
            while(r < Q[i].r) {
                r++;
                add(r);
            }
            while(r > Q[i].r) {
                del(r);
                r--;
            }
            ans[Q[i].id] = res;
        }
        for(int i = 1; i <= m; i++) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

NBUT - 1457【Sona】

https://cn.vjudge.net/contest/304248#problem/B

题意

给定 n 个数和m 次询问,每次询问区间 [l, r] 里有每个值出现次数的立方和。

分析

离散化 + 例题一。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e5+5;

int n, m;
int block;
int r[maxn];
ll mark[maxn];
ll ans[maxn];
ll res;

struct A{
    int val, pos;
}a[maxn];

bool cmp(A x, A y) {
    return x.val < y.val;
}

struct node{
    int l, r, id;
    bool operator < (const node &a) const{
        if(l/block == a.l/block) {
            return r < a.r;
        }
        return l < a.l;
    }
}Q[maxn];

void init() {
    block = sqrt(n+0.5);
    memset(mark, 0, sizeof(mark));
    res = 0;
}

void del(int x) {
    int y = r[x];
    res -= mark[y]*mark[y]*mark[y];
    mark[y] --;
    res += mark[y]*mark[y]*mark[y];
}

void add(int x) {
    int y = r[x];
    res -= mark[y]*mark[y]*mark[y];
    mark[y] ++;
    res += mark[y]*mark[y]*mark[y];
}

int main() {
    // fopen("in.txt", "r", stdin);
    // fopen("out.txt", "w", stdout);
    while(~scanf("%d", &n)) {
        init();
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i].val);
            a[i].pos = i;
        }
        sort(a+1, a+1+n, cmp);
        int cnt = 1;
        r[a[1].pos] = 1;
        for(int i = 2; i <= n; i++) {
            if(a[i].val == a[i-1].val) {
                r[a[i].pos] = cnt;
            }
            else {
                r[a[i].pos] = ++cnt;
            }
        }
        scanf("%d", &m);
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &Q[i].l, &Q[i].r);
            Q[i].id = i;
        }
        sort(Q+1, Q+1+m);
        int l = 1, r = 0;
        for(int i = 1; i <= m; i++) {
            while(l < Q[i].l) {
                del(l);
                l++;
            }
            while(l > Q[i].l) {
                l--;
                add(l);
            }
            while(r < Q[i].r) {
                r++;
                add(r);
            }
            while(r > Q[i].r) {
                del(r);
                r--;
            }
            ans[Q[i].id] = res;
        }
        for(int i = 1; i <= m; i++) {
            printf("%I64d\n", ans[i]);
        }
    }
    return 0;
}

SPOJ - DQUERY【D-query】

https://cn.vjudge.net/contest/304248#problem/I

和 LightOJ - 1188【Fast Queries】 完全一致

https://cn.vjudge.net/contest/304248#problem/K

题意

给定 n 个数和m 次询问,每次询问区间 [l, r] 每个值出现过几次。

分析

没得分析了....

代码

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e6+5;

int n, m;
int block;
int a[maxn];
int mark[maxn];
int ans[maxn];
int res;

struct node{
    int l, r, id;
    bool operator < (const node &a) const {
        if(l/block == a.l/block) {
            return r < a.r;
        }
        return l < a.l;
    }
}Q[maxn];

void init() {
    block = sqrt(n);
    memset(mark, 0, sizeof(mark));
    res = 0;
}

void del(int x) {
    if(mark[a[x]] == 1) {
        res --;
    }
    mark[a[x]] --;
}

void add(int x) {
    if(mark[a[x]] == 0) {
        res ++;
    }
    mark[a[x]] ++;
}

int main() {
    // fopen("in.txt", "r", stdin);
    // fopen("out.txt", "w", stdout);
    while(~scanf("%d", &n)) {
        init();
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        scanf("%d", &m);
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &Q[i].l, &Q[i].r);
            Q[i].id = i;
        }
        sort(Q+1, Q+1+m);
        int l = 1, r = 0;
        for(int i = 1; i <= m; i++) {
            while(l < Q[i].l) {
                del(l);
                l++;
            }
            while(l > Q[i].l) {
                l--;
                add(l);
            }

            while(r < Q[i].r) {
                r++;
                add(r);
            }
            while(r > Q[i].r) {
                del(r);
                r--;
            }
            ans[Q[i].id] = res;
        }
        for(int i = 1; i <= m; i++) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

CodeForces - 86D【Powerful array】

https://cn.vjudge.net/contest/304248#problem/J

题意

给定 n 个数和m 次询问,设区间 [l, r] 内每个数出现次数为 \(mark[a_i]\) ,输出 \(mark[a_i]*mark[a_i]*a_i\) 的和。

分析

没得分析了....

代码

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e6+5;

int n, m;
int block;
ll a[maxn];
ll mark[maxn];
ll ans[maxn];
ll res;

struct node {
    int l, r, id;
    bool operator < (const node &a) const {
        if(l/block == a.l/block) {
            return r < a.r;
        }
        return l < a.l;
    }
}Q[maxn];

void init() {
    memset(mark, 0, sizeof(mark));
    res = 0;
    block = sqrt(n+0.5);
}

void del(int x) {
    res -= mark[a[x]]*mark[a[x]]*a[x];
    mark[a[x]] --;
    res += mark[a[x]]*mark[a[x]]*a[x];
}

void add(int x) {
    res -= mark[a[x]]*mark[a[x]]*a[x];
    mark[a[x]] ++;
    res += mark[a[x]]*mark[a[x]]*a[x];
}

int main() {
    // fopen("in.txt", "r", stdin);
    // fopen("out.txt", "w", stdout);
    while(~scanf("%d%d", &n, &m)) {
        init();
        for(int i = 1; i <= n; i++) {
            scanf("%I64d", &a[i]);
        }
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &Q[i].l, &Q[i].r);
            Q[i].id = i;
        }
        sort(Q+1, Q+1+m);
        int l = 1, r = 0;
        for(int i = 1; i <= m; i++) {
            while(l < Q[i].l) {
                del(l);
                l++;
            }
            while(l > Q[i].l) {
                l--;
                add(l);
            }
            while(r < Q[i].r) {
                r++;
                add(r);
            }
            while(r > Q[i].r) {
                del(r);
                r--;
            }
            ans[Q[i].id] = res;
        }
        for(int i = 1; i <= m; i++) {
            printf("%I64d\n", ans[i]);
        }
    }
    return 0;
}
posted @ 2019-05-27 19:43  Decray  阅读(258)  评论(0编辑  收藏  举报