莫队-题1

XOR and Favorite Number

image-20230825223422628

题解

  • 我们考虑将问题进行转化
  • 对于一个区间\([l,r]\)中有多少个子区间异或值为\(k\)这个问题,我们考虑到异或的前缀性质,对其进行差分
  • 即:令\(pre_i\)\([1,i]\)的前缀异或和,对于\(i,j \in [l,r],i\leq j\),且\(pre_{i - 1} \oplus pre_j=k\)
  • 那么问题转化为:区间\([l,r]\)中有多少个二元组\((i,j)\),满足\(pre_{i - 1} \oplus pre_j=k\)
  • 而莫队恰好适用于解决区间二元组的问题,令\(cnt[x]\)代表\(x\)的出现次数
  • 我们考虑\(add\)操作:先查询\(ans +=cnt[pre_i \oplus k]\),再修改\(cnt[pre_i]++\)
  • \(delete\)操作是一样的
  • \(trick\):将询问的\(l\)提前\(-1\),即\(l:=l-1\)
  • 注意开$long \ long $
const int N = 1e5 + 10, M = 1e7 + 10;
const int B = sqrt(N) + 1;

int n, q, k;
int a[N], pre[N];
int cnt[M], id[N], ans[N], preAns;

struct QUERY
{
    int l, r, idx;
    bool operator<(const QUERY &t) const
    {
        if (id[l] == id[t.l])
        {
            if (id[l] & 1)
                return r < t.r;
            else
                return r > t.r;
        }
        else
            return l < t.l;
    }
} qry[N];

void build()
{
    for (int i = 1; i <= n; ++i)
        id[i] = i / B + 1;
}

void del(int i)
{
    cnt[pre[i]]--;
    preAns -= cnt[pre[i] ^ k];
}

void add(int i)
{
    preAns += cnt[pre[i] ^ k];
    cnt[pre[i]]++;
}

void solve()
{
    cin >> n >> q >> k;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    for (int i = 1; i <= n; ++i)
        pre[i] = pre[i - 1] ^ a[i];
    build();
    for (int i = 1; i <= q; ++i)
    {
        int l, r;
        cin >> l >> r;
        l--;
        qry[i] = {l, r, i};
    }
    sort(qry + 1, qry + q + 1);
    for (int i = 1, l = 1, r = 0; i <= q; ++i)
    {
        while (l > qry[i].l)
            add(--l);
        while (r < qry[i].r)
            add(++r);
        while (l < qry[i].l)
            del(l++);
        while (r > qry[i].r)
            del(r--);
        ans[qry[i].idx] = preAns;
    }
    for (int i = 1; i <= q; ++i)
        cout << ans[i] << endl;
}

P5268 [SNOI2017] 一个简单的询问

image-20230825235417087

image-20230825235427890

题解:双前缀莫队

  • 我们将题目转化成区间二元组的形式:

\([l_1, r_1]\)中选一个\(i\),从\([l_2,r_2]\)中选一个\(j\),询问有多少个二元组\((i,j)\)满足\(a_i = a_j\)

  • 但是这个是从两个区间中分别选,怎样简化呢?

  • 我们考虑差分

\(f(l_1, r_1,l_2,r_2)\)为从\([l_1, r_1]\)中选一个\(i\),从\([l_2,r_2]\)中选一个\(j\),且\(a_i=a_j\)的方案数

\(\sum get(l_1,r_1,x)\times get(l_2,r_2,x) = f(l_1, r_1,l_2,r_2)\)

假设我们从\([l_2,r_2]\)中选择了一个\(j\),那么方案数可以差分成:从\([1,r_1]\)中选择一个\(i\),使得\(a_i=a_j\)的方案数 减去 从\([1,l_1 - 1]\)中选择一个\(i\),使得\(a_i=a_j\)的方案数,即

\[ f(l_1, r_1,l_2,r_2) = f(1,r_1,l_2,r_2) - f(1,l_1-1,l_2,r_2) \]

那么,同理,我们也可以从\([l_1,r_1]\)中选一个\(i\),对区间\([l_2,r_2]\)进行差分,得到:

\[ f(l_1, r_1,l_2,r_2) \\ =f(1,r_1,l_2,r_2) - f(1,l_1-1,l_2,r_2)\\ =f(1,r_1,1,r_2) - f(1,r_1,1,l_2-1)-f(1,l_1-1,1,r_2)+f(1,l_1-1,1,l_2-1) \]

那么我们就将一个询问转化为了\(4\)个询问,只不过莫队维护的是双前缀的答案

  • 我们令\(cnt1[x]\)为前缀\([1,l]\)\(x\)出现的次数,\(cnt2[x]\)为前缀\([1,r]\)\(x\)出现的个数

  • 比如说我们在\([1,l]\)进行\(add\)操作变为\([1,l+1]\)时,设\(a[l+1]=x\)对答案的维护应该为:

\[ ans += cnt2[x]\\ cnt1[x]++ \]

  • 同时,因为维护的是前缀,所以我们需要调整一下莫队修改的方式
const int N = 5e4 + 10, M = 1e7 + 10;
const int B = sqrt(N) + 1;

int n, q, a[N];
int cnt1[N], cnt2[N], id[N], ans[N][4], preAns;

struct QUERY
{
    int l, r, idx1, idx2;
    bool operator<(const QUERY &t) const
    {
        if (id[l] == id[t.l])
        {
            if (id[l] & 1)
                return r < t.r;
            else
                return r > t.r;
        }
        else
            return l < t.l;
    }
} qry[N << 2];

void build()
{
    for (int i = 1; i <= n; ++i)
        id[i] = i / B + 1;
}

void del(int x, int type)
{
    if (type == 1)
    {
        cnt1[x]--;
        preAns -= cnt2[x];
    }
    else
    {
        cnt2[x]--;
        preAns -= cnt1[x];
    }
}

void add(int x, int type)
{
    if (type == 1)
    {
        preAns += cnt2[x];
        cnt1[x]++;
    }
    else
    {
        preAns += cnt1[x];
        cnt2[x]++;
    }
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    build();
    cin >> q;
    int idx = 0;
    for (int i = 1; i <= q; ++i)
    {
        int l1, r1, l2, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        // 一个询问转化为4个询问
        qry[++idx] = {r1, r2, i, 0};
        qry[++idx] = {r1, l2 - 1, i, 1};
        qry[++idx] = {l1 - 1, r2, i, 2};
        qry[++idx] = {l1 - 1, l2 - 1, i, 3};
    }
    sort(qry + 1, qry + idx + 1);
    for (int i = 1, l = 0, r = 0; i <= idx; ++i)
    {
        if (qry[i].l > qry[i].r)
            swap(qry[i].l, qry[i].r);
        // 调整莫队的顺序
        while (r < qry[i].r)
            add(a[++r], 2);
        while (l < qry[i].l)
            add(a[++l], 1);
        while (l > qry[i].l)
            del(a[l--], 1);
        while (r > qry[i].r)
            del(a[r--], 2);
        ans[qry[i].idx1][qry[i].idx2] = preAns;
    }
    for (int i = 1; i <= q; ++i)
        cout << ans[i][0] - ans[i][1] - ans[i][2] + ans[i][3] << endl;
}

P3245 [HNOI2016] 大数

image-20230826123012576

image-20230826124203617

题解:后缀差分

  • 该题显然是询问区间中子区间个数的问题,将其转换为区间二元组问题

  • 考虑后缀差分,令\(suf[i]\)\([i,n]\)数字串所形成的数字

那么\([l,r]\)数字串为\(\frac{suf[l] - suf[r + 1]}{10^{n - r}}\)

如果\([l,r]\)的数字串为\(p\)的倍数,则

\[\frac{suf[l] - suf[r + 1]}{10^{n - r}} \% p = 0\\ \]

\(p \neq 2 \ and \ 5\)时,\(10^{n - r}\)\(p\)互质,所以:

\[(suf[l] - suf[r + 1]) \% p = 0 \\ suf[l] \% p = suf[r + 1] \% p \]

\(suf[i]\)\([i,n]\)数字串所形成的数字\(\%p\)后的余数

那么题目就转化为:询问在区间\([l,r]\)中二元组\((i,j)\),满足\(suf[i] = suf[j + 1]\)的个数

显然可以用莫队解决,注意\(p\leq10^9\),所以需要对\(suf[i]\)进行离散化

  • \(p = 2 \ or \ 5\)的时候考虑特判

显然如果一个数字串结尾的数字\(\%p=0\),那么那么该数字前面的包含该数字的子串都是\(p\)的倍数

所以如果\(s[i]\%p=0,i \in[l,r]\),那么第\(i\)位数字对答案的贡献为\(i - l + 1\)

所以对于一个区间的询问,我们考虑预处理前缀答案以及前缀个数即可

const int N = 2e5 + 10, M = 4e5 + 10;
const int B = sqrt(N) + 1;

int n, p, q, pre1[N], pre2[N], suf[N];
int id[N], ans[N], preAns, cnt[N];
string s;

struct QUERY
{
    int l, r, idx;
    bool operator<(const QUERY &t) const
    {
        if (id[l] == id[t.l])
        {
            if (id[l] & 1)
                return r < t.r;
            else
                return r > t.r;
        }
        else
            return l < t.l;
    }
} qry[N];

void build()
{
    for (int i = 1; i <= n + 1; ++i)
        id[i] = (i - 1) / B + 1;
}

void del(int i)
{
    cnt[suf[i]]--;
    preAns -= cnt[suf[i]];
}

void add(int i)
{
    preAns += cnt[suf[i]];
    cnt[suf[i]]++;
}

void cal()
{
    for (int i = 1; i <= n; ++i)
    {
        if ((s[i] - '0') % p == 0)
        {
            pre1[i] = pre1[i - 1] + i;
            pre2[i] = pre2[i - 1] + 1;
        }
        else
        {
            pre1[i] = pre1[i - 1];
            pre2[i] = pre2[i - 1];
        }
    }
    cin >> q;
    while (q--)
    {
        int l, r;
        cin >> l >> r;
        int res = pre1[r] - pre1[l - 1];
        cout << res - (pre2[r] - pre2[l - 1]) * (l - 1) << endl;
    }
}

void solve()
{
    cin >> p >> s;
    n = s.length();
    s = " " + s;
    // p = 2 or 5 时特判
    if (p == 2 || p == 5)
    {
        cal();
        return;
    }
    build();
    vector<int> vec;
    int pow = 1;
    suf[n + 1] = 0;
    // 最后一个点也要离散化
    vec.push_back(suf[n + 1]);
    for (int i = n; i >= 1; --i)
    {
        suf[i] = (suf[i + 1] + (s[i] - '0') * pow % p) % p;
        pow = pow * 10 % p;
        vec.push_back(suf[i]);
    }
    sort(all(vec));
    vec.erase(unique(all(vec)), vec.end());
    auto find = [&](int x)
    {
        return lower_bound(all(vec), x) - vec.begin() + 1;
    };
    for (int i = 1; i <= n + 1; ++i)
        suf[i] = find(suf[i]);
    cin >> q;
    for (int i = 1; i <= q; ++i)
    {
        int l, r;
        cin >> l >> r;
        qry[i] = {l, r + 1, i};
    }
    sort(qry + 1, qry + q + 1);
    for (int i = 1, l = 1, r = 0; i <= q; ++i)
    {
        while (l > qry[i].l)
            add(--l);
        while (r < qry[i].r)
            add(++r);
        while (l < qry[i].l)
            del(l++);
        while (r > qry[i].r)
            del(r--);
        ans[qry[i].idx] = preAns;
    }
    for (int i = 1; i <= q; ++i)
        cout << ans[i] << endl;
}
posted @ 2023-08-26 12:50  Zeoy_kkk  阅读(8)  评论(0编辑  收藏  举报