莫队总结

省赛打得跟屎一样=_=,队友有思路了然鹅我们都不会写莫队,调不出来。回来恶补一波莫队。

不带修改莫队

通过对离线询问的一个玄学排序,使得左右指针移动的次数都相对的少,减少转移的次数
排序:左端点不同块的询问,按左端点所属块号升序排; 左端点同块的询问,按右端点升序排
奇偶性优化:对于左端点同块的询问,若左端点块号为奇数,按右端点升序排;为偶数则按右端点降序排。 可以减少右端点移动的次数。

题意:查询区间[l, r]中差小于等于k的 a i a_i ai a j a_j aj的对数
解法:用权值树状数组维护莫队当前 [ l , r ] [l, r] [l,r]中的各数的数量,莫队增加一个数x时,x贡献的答案为query(x - k, x + k),删除时同理
放一个回来写的省赛c代码,因为不能补,不能保证没bug。
(5.19更新)
今天在hdu开重现了,wa了几发。树状数组开小了,因为最多可能一共有3n个点。乘个3能过。

#include <bits/stdc++.h>

#define query(x, y) (sum(y) - sum(x-1))
using namespace std;
int n, m, k, a[30000], len, ans[30000];
vector<int> b;

struct node {
    int l, r, id;

    bool operator<(const node &a) const {
        if (l / len == a.l / len)
            return (l / len) & 1 ? r < a.r : r > a.r;
        return l / len < a.l / len;
    }
} q[30000];

struct {
    int l, m, r;
} id[30000];

int c[30000];

void add(int p, int val) {
    for (int i = p; i <= n; i += i & -i) {
        c[i] += val;
    }
}

int sum(int p) {
    int ans = 0;
    for (int i = p; i; i -= i & -i) {
        ans += c[i];
    }
    return ans;
}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    len = sqrt(n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", a + i);
        b.push_back(a[i]);
        b.push_back(a[i] + k);
        b.push_back(a[i] - k);
    }
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    for (int i = 1; i <= n; ++i) {
        id[i].m = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1;
        id[i].l = lower_bound(b.begin(), b.end(), a[i] - k) - b.begin() + 1;
        id[i].r = lower_bound(b.begin(), b.end(), a[i] + k) - b.begin() + 1;
    }
    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &q[i].l, &q[i].r);
        q[i].id = i;
    }
    sort(q + 1, q + m + 1);
    int l = 1, r = 0, now = 0;
    n = b.size();
    for (int i = 1; i <= m; ++i) {
        while (l > q[i].l) {
            --l;
            now += query(id[l].l, id[l].r);
            add(id[l].m, 1);
        }
        while (r < q[i].r) {
            ++r;
            now += query(id[r].l, id[r].r);
            add(id[r].m, 1);
        }
        while (l < q[i].l) {
            add(id[l].m, -1);
            now -= query(id[l].l, id[l].r);
            ++l;
        }
        while (r > q[i].r) {
            add(id[r].m, -1);
            now -= query(id[r].l, id[r].r);
            --r;
        }
        ans[q[i].id] = now;
    }
    for (int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}

带修改莫队

有修改的时候块大小要开成 n 2 / 3 n^{2/3} n2/3才能使时间复杂度达到最优( O ( n 5 / 3 ) O(n^{5/3}) O(n5/3))
在前面的基础上加上一维时间,并把询问和修改分别记录。
对于两个询问,若左端点块号和右端点都相同,则按时间升序排序
在莫队的四个while处理好区间后,再用两个while把当前时间调整到询问的时间。
对于一个修改,在time增加时处理到它时应把a[i]改为val, 那么在time减少又遇到它时,显然应该把当前的a[i]改回原来的a[i]
所以处理修改时直接swap(a[i], val),把原来的值存到修改里面,详情见代码

BZOJ2120数颜色

#include <bits/stdc++.h>

#define query(x, y) (sum(y) - sum(x-1))
using namespace std;
const int maxn = 50005;
int n, m, a[maxn], len, ans[maxn];

struct node {
    int l, r, t, id;

    node() { t = 0; }

    bool operator<(const node &a) const {
        if (l / len != a.l / len)
            return l / len < a.l / len;
        if (r != a.r)
            return (l / len) & 1 ? r < a.r : r > a.r;
        return t < a.t;
    }
} q[maxn];

struct {
    int pos, val;//修改的位置和值
} c[maxn];
int cnt[1000005];

int main() {
    scanf("%d%d", &n, &m);
    len = (int) pow(n, 2.0 / 3) + 1;
    for (int i = 1; i <= n; ++i) {
        scanf("%d", a + i);
    }
    char op[5];
    int l, r, qnum = 0, cnum = 0;
    for (int i = 1; i <= m; ++i) {
        scanf("%s", op);
        if (op[0] == 'Q') {
            ++qnum;
            scanf("%d%d", &q[qnum].l, &q[qnum].r);
            q[qnum].id = qnum;
            q[qnum].t = cnum;
        }
        else {
            ++cnum;
            scanf("%d%d", &c[cnum].pos, &c[cnum].val);
        }
    }
    sort(q + 1, q + qnum + 1);
    l = 1, r = 0;
    int now = 0, time = 0;
    for (int i = 1; i <= m; ++i) {
        while (l > q[i].l) {
            if (++cnt[a[--l]] == 1)
                ++now;
        }
        while (r < q[i].r) {
            if (++cnt[a[++r]] == 1)
                ++now;
        }
        while (l < q[i].l) {
            if (--cnt[a[l++]] == 0)
                --now;
        }
        while (r > q[i].r) {
            if (--cnt[a[r--]] == 0)
                --now;
        }
        while (time < q[i].t) {
            ++time;
            if (l <= c[time].pos && c[time].pos <= r) {
                if (--cnt[a[c[time].pos]] == 0)
                    --now;
                if (++cnt[c[time].val] == 1)
                    ++now;
            }
            swap(c[time].val, a[c[time].pos]);
        }
        while (time > q[i].t) {
            if (l <= c[time].pos && c[time].pos <= r) {
                if (--cnt[a[c[time].pos]] == 0)
                    --now;
                if (++cnt[c[time].val] == 1)
                    ++now;
            }
            swap(c[time].val, a[c[time].pos]);
            --time;
        }
        ans[q[i].id] = now;
    }
    for (int i = 1; i <= qnum; ++i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}

树上莫队

对于子树询问,dfs序把树拍平乱搞就行了
链上的询问还没学,好像是欧拉序?这个之后补

放一个子树询问的代码。
CodeForces - 375D Tree and Queries
询问子树中数量大于等于k的颜色的数量

#include <bits/stdc++.h>

using namespace std;

const int maxn = 100005;
vector<int> G[maxn];
int color[maxn], cnt, L[maxn], R[maxn], ans[maxn], c[maxn], a[maxn], ANS[maxn];

void dfs(int u, int fa) {
    L[u] = ++cnt;
    c[cnt] = color[u];
    for (int &v:G[u]) {
        if (v == fa) continue;
        dfs(v, u);
    }
    R[u] = cnt;
}

int len;

struct node {
    int l, r, k, id;

    bool operator<(const node &b) const {
        if (l / len != b.l / len)
            return l / len < b.l / len;
        return l / len & 1 ? r < b.r : r > b.r;
    }
} q[maxn];

int num[maxn];

int main() {
    int n, m, u, v, k;
    cin >> n >> m;
    len = sqrt(n);
    for (int i = 1; i <= n; ++i) {
        cin >> color[i];
    }
    for (int i = 0; i < n - 1; ++i) {
        cin >> u >> v;
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }
    dfs(1, 1);
    for (int i = 1; i <= m; ++i) {
        cin >> v >> q[i].k;
        q[i].l = L[v];
        q[i].r = R[v];
        q[i].id = i;
    }
    sort(q + 1, q + m + 1);
    int l = 1, r = 0;
    for (int i = 1; i <= m; ++i) {
        while (l > q[i].l) {
            ANS[++num[c[--l]]]++;
        }
        while (r < q[i].r) {
            ANS[++num[c[++r]]]++;

        }
        while (l < q[i].l) {
            ANS[num[c[l++]]--]--;

        }
        while (r > q[i].r) {
            ANS[num[c[r--]]--]--;
        }
        ans[q[i].id] = ANS[q[i].k];
    }
    for (int i = 1; i <= m; ++i) {
        cout << ans[i] << '\n';
    }
    return 0;
}

也可以树上启发式合并

#include <bits/stdc++.h>

using namespace std;
const int maxn = 100005;
int n, m, color[maxn], ans[maxn];
vector<int> G[maxn];

int siz[maxn], son[maxn];

void dfs1(int u, int fa)
{
    siz[u] = 1;
    for (int &v:G[u])
    {
        if (v == fa) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if (siz[v] > siz[son[u]])
            son[u] = v;
    }
}

struct query
{
    int k, id;
};
vector<query> q[maxn];
int cntColor[maxn], num[maxn];

bool vis[maxn];

void solve(int u, int fa, int val)
{
    if (val == -1)
        --num[cntColor[color[u]]--];
    else
        ++num[++cntColor[color[u]]];
    for (int &v:G[u])
        if (v != fa && !vis[v])
            solve(v, u, val);
}

void dfs2(int u, int fa, bool flag)
{
    for (int &v:G[u])
    {
        if (v == fa || v == son[u]) continue;
        dfs2(v, u, true);
    }
    if (son[u])
    {
        dfs2(son[u], u, false);
        vis[son[u]] = true;
    }
    solve(u, fa, 1);
    for (auto i:q[u])
        ans[i.id] = num[i.k];
    if (son[u])
        vis[son[u]] = false;
    if (flag)
        solve(u, fa, -1);//消去轻儿子的贡献
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> color[i];
    int u, v;
    for (int i = 1; i < n; ++i)
    {
        cin >> u >> v;
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }
    int k;
    for (int i = 1; i <= m; ++i)
    {
        cin >> u >> k;
        q[u].push_back({k, i});
    }
    dfs1(1,1);
    dfs2(1,1, true);
    for (int i = 1; i <= m; ++i)
        cout << ans[i] << '\n';
    return 0;
}
posted @ 2019-05-17 14:33  Apale  阅读(45)  评论(0编辑  收藏  举报