莫队-题1
XOR and Favorite Number
题解
- 我们考虑将问题进行转化
- 对于一个区间\([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] 一个简单的询问
题解:双前缀莫队
- 我们将题目转化成区间二元组的形式:
从\([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] 大数
题解:后缀差分
该题显然是询问区间中子区间个数的问题,将其转换为区间二元组问题
考虑后缀差分,令\(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;
}