kuangbin 莫队专题
kuangbin 莫队专题
这几天把框斌的莫队都刷完了, 还是有不少收获的, 之前不会证明莫对的时间复杂度, 现在也能口胡证明了。
莫队和其它数据结构不一样,会了之后就很简单。只要是关于区间o(1)算出贡献都能用莫队解决。
具体看下面的题目吧!
Sona
题解:求区间每个数出现的次数的立方和
题解:刚学莫队的时候肯定知道求区间不同颜色的个数, 但是如何求不同颜色的个数的立方和了。
这里有个和巧妙的做法就是, 用vis数组记录当前颜色出现的个数, 当出现颜色时, 先减去之前的个数
立方和在加上现在颜色的立法和, 这样不断跟新最后一次一定是这个区间 这个颜色出现的总次数了。
#include<iostream>
#include<vector>
#include<algorithm>
#include<math.h>
using namespace std;
const int N = 1e5 + 7;
struct query {
int l, r, pos;
}q[N];
typedef long long ll;
ll n, a[N], vis[N], res = 0, m, ans[N];
int block[N];
vector<ll> g;
bool cmp(query x, query y) {
if (block[x.l] == block[y.l]) {
return x.r < y.r;
}
return block[x.l] < block[y.l];
}
int get_id(ll x) {
return lower_bound(g.begin(), g.end(), x) - g.begin() + 1;
}
void add(int pos) {
res -= vis[a[pos]] * vis[a[pos]] * vis[a[pos]];
vis[a[pos]]++;
res += vis[a[pos]] * vis[a[pos]] * vis[a[pos]];
}
void del(int pos) {
res -= vis[a[pos]] * vis[a[pos]] * vis[a[pos]];
vis[a[pos]]--;
res += vis[a[pos]] * vis[a[pos]] * vis[a[pos]];
}
int main() {
while(~scanf("%lld", &n)){
g.clear();
res = 0;
for (int i = 0; i <= n + 10; i++) {
vis[i] = 0;
}
int b = sqrt(n*1.0);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
g.push_back(a[i]);
block[i] = i / b;
}
sort(g.begin(), g.end());
g.erase(unique(g.begin(), g.end()), g.end());
for (int i = 1; i <= n; i++) {
a[i] = get_id(a[i]);
}
scanf("%lld", &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &q[i].l, &q[i].r);
if (q[i].l > q[i].r) swap(q[i].l, q[i].r);
q[i].pos = i;
}
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0;
for (int i = 1; i <= m; i++) {
while (l < q[i].l) {
del(l++);
}
while (l > q[i].l) {
add(--l);
}
while (r < q[i].r) {
add(++r);
}
while (r > q[i].r) {
del(r--);
}
ans[q[i].pos] = res;
}
for (int i = 1; i <= m; i++) {
printf("%lld\n", ans[i]);
}
}
}
Little Elephant and Array
题意:问区间有多少个数出现的次数是这个数本身
题解:假设这个数字为x, 用vis数组记录每个数出现的个数, 如果刚好等于x那么答案加1如果刚好等于x + 1那么说明之前那个贡献就删掉 答案就减1.
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
struct query {
int l, r, pos;
}q[N];
vector<ll> g;
int n, m, block[N];
ll a[N], ans[N], res = 0, vis[N];
void add(int pos) {
if (a[pos] > n) return;
vis[a[pos]]++;
if (vis[a[pos]] == a[pos]) {
res++;
} else if (vis[a[pos]] == a[pos] + 1) {
res--;
}
}
void del(int pos) {
if (a[pos] > n) return;
vis[a[pos]]--;
if (vis[a[pos]] == a[pos]) {
res++;
} else if (vis[a[pos]] == a[pos] - 1) {
res--;
}
}
bool cmp(query x, query y) {
if (block[x.l] == block[y.l]) {
return x.r < y.r;
}
return block[x.l] < block[y.l];
}
int main() {
scanf("%d %d", &n, &m);
int b = sqrt(n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
block[i] = i / b;
}
for (int i = 1; i <= m; i++) {
scanf("%d %d", &q[i].l, &q[i].r);
q[i].pos = i;
}
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0;
for (int i = 1; i <= m; i++) {
while (l < q[i].l) {
del(l++);
}
while (l > q[i].l) {
add(--l);
}
while (r < q[i].r) {
add(++r);
}
while (r > q[i].r) {
del(r--);
}
ans[q[i].pos] = res;
}
for (int i = 1; i <= m; i++) {
printf("%lld\n", ans[i]);
}
}
Machine Learning
题意:给你n个数,让你求区间mex(0不算)和单点修改
题解:带修莫队板子题吧, 带修莫队和普通莫队差不多多了一维时间, 每次暴力的时进行暴力修改, 然后要注意的是带修莫队分块是\(n^{0.6666666}\)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 7;
struct query {
int l, r, t, id;
}q[N];
struct option {
int pos, v;
}p[N], back[N];
int block[N], ans[N], vis[N], maxn[N], n, m, a[N],A[N];
bool cmp (query x, query y) {
if (block[x.l] == block[y.l]) {
if (block[x.r] == block[y.r]) {
return x.t < y.t;
}
return x.r < y.r;
}
return x.l < y.l;
}
vector<int> g;
int get_id(int x) {
return lower_bound(g.begin(), g.end(), x) - g.begin() + 1;
}
void update(int pos, int value) {
a[pos] = value;
}
void add(int pos) {
maxn[vis[a[pos]]]--;
vis[a[pos]]++;
maxn[vis[a[pos]]]++;
}
void del(int pos) {
maxn[vis[a[pos]]]--;
vis[a[pos]]--;
maxn[vis[a[pos]]]++;
}
int main() {
scanf("%d %d", &n, &m);
int b = pow(n, 0.666666);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
A[i] = a[i];
g.push_back(a[i]);
block[i] = i / b;
}
int total = 0;
int cnt = 1;
for (int i = 1; i <= m; i++) {
int op; scanf("%d", &op);
if (op == 1) {
scanf("%d %d", &q[cnt].l, &q[cnt].r);
q[cnt].id = i;
q[cnt].t = total;
cnt++;
} else {
total++;
scanf("%d %d", &p[total].pos, &p[total].v);
back[total].v = A[p[total].pos];
back[total].pos = p[total].pos;
A[p[total].pos] = p[total].v;
g.push_back(p[total].v);
}
}
sort(g.begin(), g.end());
g.erase(unique(g.begin(), g.end()), g.end());
for (int i = 1; i <= n; i++) {
a[i] = get_id(a[i]);
}
for (int i = 1; i <= total; i++) {
p[i].v = get_id(p[i].v);
back[i].v = get_id(back[i].v);
}
sort(q + 1, q + cnt, cmp);
int l = 1, r = 0, now = 0;
for (int i = 1; i < cnt; i++) {
while (now < q[i].t) {
now++;
if (p[now].pos >= l && p[now].pos <= r) {
maxn[vis[a[p[now].pos]]]--;
vis[a[p[now].pos]]--;
maxn[vis[a[p[now].pos]]]++;
maxn[vis[p[now].v]]--;
vis[p[now].v]++;
maxn[vis[p[now].v]]++;
}
update(p[now].pos, p[now].v);
}
while (now > q[i].t) {
if (l <= back[now].pos && r >= back[now].pos) {
maxn[vis[a[back[now].pos]]]--;
vis[a[back[now].pos]]--;
maxn[vis[a[back[now].pos]]]++;
maxn[vis[back[now].v]]--;
vis[back[now].v]++;
maxn[vis[back[now].v]]++;
}
update(back[now].pos, back[now].v);
now--;
}
while (r < q[i].r) {
add(++r);
}
while (l < q[i].l) {
del(l++);
}
while (l > q[i].l) {
add(--l);
}
while (r > q[i].r) {
del(r--);
}
for (int j = 1; j <= 10000; j++) {
if (maxn[j] == 0) {
ans[q[i].id] = j;
break;
}
}
}
for (int i = 1; i <= m; i++) {
if (ans[i]) {
printf("%d\n", ans[i]);
}
}
}
Can you answer these queries II[https://vjudge.net/problem/SPOJ-GSS2]
题意:求区间最大连续子串的和, 重复的数字只算一次。
题解:这题不知道莫队咋求解, 网上貌似没用莫队的解法, 基本上都是用线段树解决这题的
线段树操作这题第一步是离线, 将所有操作按照r进行从小到大排序。
为啥要按照r从小到大排序?
先想一下如何暴力求解?
如果给你一个区间让你求最大子串和, 很多少人想的时\(n ^ {2}\)暴力, 其实只需要 O(n)暴力就可以解决
for (int i = l; i <= r; i++) {
sum += a[i];
ans = max(ans, sum);
sum = max(0, sum);
}
那能不能用线段树o(n) * log(n)维护所有区间答案呢?
答案是可以的。
假设线段现在插入的是第i个数那么
第一个节点维护的信息是
\(a[1] + a[2] + a[3]+......+a[i]\)
第二个节点维护的信息是
\(a[2] + a[3] + a[4] + ......+ a[i]\)
第三个节点维护的信息是
\(a[3] + a[4] + ...... + a[i]\)
第i个节点维护的信息是
\(a[i]\)
那么插入到第i + 1个信息也是相当于区间1到i + 1加上a[i + 1]
线段树每个节点还有在维护一个 从当前节点开始到i的所有子串的最大值
如果线段树更新到第i个节点
那么 询问操作中 r == i的答案一定在 l 到r中的最大值。
因为线段树每个节点维护了从当前节点开始到i的所有子串的最大值。所以答案肯定在这个区间里面。
这就是之前为啥询问要按照r从小到大排序。
难点还是线段树的懒标记更新
去重的话自己仔细想一下应该可以想出来。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
struct segment {
ll sum, maxn;
}tree[4 * N];
#define m (l + r) / 2
#define lson 2 * node
#define rson 2 * node + 1
ll a[N], ans[N];
int flag[4 * N], fmaxn[4 * N];
void push_down(int node) {
if (fmaxn[node]) {
tree[lson].maxn = max(tree[lson].maxn, fmaxn[node] + tree[lson].sum);
tree[rson].maxn = max(tree[rson].maxn, tree[rson].sum + fmaxn[node]);
fmaxn[lson] = max(fmaxn[lson], fmaxn[node] + flag[lson]);
fmaxn[rson] = max(fmaxn[rson], fmaxn[node] + flag[rson]);
fmaxn[node] = 0;
}
if (flag[node]) {
tree[lson].sum += flag[node];
tree[rson].sum += flag[node];
flag[lson] += flag[node];
flag[rson] += flag[node];
flag[node] = 0;
}
}
void update(ll v, int ql, int qr, int l, int r, int node) {
if (ql <= l && qr >= r) {
tree[node].sum += v;
tree[node].maxn = max(tree[node].maxn, tree[node].sum);
flag[node] += v;
fmaxn[node] = max(fmaxn[node], flag[node]);
return;
}
push_down(node);
if (ql <= m) update(v, ql, qr, l, m, lson);
if (qr > m) update(v, ql, qr, m + 1, r, rson);
tree[node].sum = max(tree[lson].sum, tree[rson].sum);
tree[node].maxn = max(tree[lson].maxn, tree[rson].maxn);
}
ll query(int ql, int qr, int l, int r, int node) {
if (ql <= l && qr >= r) {
return tree[node].maxn;
}
ll ans = INT_MIN;
push_down(node);
if (ql <= m) ans = max(ans, query(ql, qr, l, m, lson));
if (qr > m) ans = max(ans, query(ql, qr, m + 1, r, rson));
return ans;
}
map<int , int>vis;
int pos[N];
struct qu{
int l, r, id;
}p[N];
int main() {
int n, q;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
pos[i] = vis[a[i]];
vis[a[i]] = i;
}
scanf("%d", &q);
for (int i = 1; i <= q; i++) {
int l, r; scanf("%d %d", &p[i].l, &p[i].r);
p[i].id = i;
}
sort(p + 1, p + 1 + q, [](qu x, qu y) {
return x.r < y.r;
});
int l = 1;
for (int i = 1; i <= n; i++) {
update(a[i], pos[i] + 1, i, 1, n, 1);
while(p[l].r == i) {
ans[p[l].id] = query(p[l].l, p[l].r, 1, n, 1);
l++;
}
}
for (int i = 1; i <= q; i++) {
printf("%lld\n", ans[i]);
}
}