目录
一般的莫队比较简单, 考虑从[l, r] 到 [l, r+1]的变化一般都能解决掉, 这里放3道例题
例1: BZOJ3289 Mato的文件管理
因为相邻的交换最多消除一个逆序对, 所以逆序对个数就是交换次数, 莫队套个树状数组就好了
#include<bits/stdc++.h>
#define N 500050
using namespace std;
typedef long long ll;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, m, b[N], siz, a[N], pos[N], L[N], R[N], cnt; ll c[N], sum, ans[N];
struct Node{ int l, r, id;}q[N];
bool cmp(Node a, Node b){
if(pos[a.l] == pos[b.l]) return a.r < b.r;
return pos[a.l] < pos[b.l];
}
void Add(int x, int val){for(;x<=n;x+=x&-x) c[x] += val;}
ll Ask(int x){ll ans=0; for(;x;x-=x&-x) ans += c[x]; return ans;}
void suf_Add(int x){ sum += Ask(n) - Ask(x); Add(x, 1);}
void suf_Del(int x){ Add(x, -1); sum -= Ask(n) - Ask(x);}
void pre_Add(int x){ sum += Ask(x-1); Add(x, 1);}
void pre_Del(int x){ Add(x, -1); sum -= Ask(x-1);}
int main(){
n = read();
for(int i=1; i<=n; i++) a[i] = b[i] = read();
sort(b+1, b+n+1); int siz = unique(b+1, b+n+1) - (b+1);
for(int i=1; i<=n; i++) a[i] = lower_bound(b+1, b+siz+1, a[i]) - b;
int S = sqrt(n);
for(int i=1; i<=n; i++) pos[i] = (i-1) / S + 1;
m = read();
for(int i=1; i<=m; i++) q[i] = (Node){read(), read(), i};
sort(q+1, q+m+1, cmp);
int l = 1, r = 0;
for(int i=1; i<=m; i++){
while(r < q[i].r) suf_Add(a[++r]); while(r > q[i].r) suf_Del(a[r--]);
while(l < q[i].l) pre_Del(a[l++]); while(l > q[i].l) pre_Add(a[--l]);
ans[q[i].id] = sum;
} for(int i=1; i<=m; i++) printf("%lld\n", ans[i]);
return 0;
}
例2: WOJ4301Gty的二逼妹子序列
比较直接的是用树状数组维护, 但考虑到O(1)修改, O(sqrt(n)) 查询会更加优秀, 所以用分块维护
#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, m, siz, a[N], pos[N], L[N], R[N], ans[N], res;
int val[N], sum[N], cnt[N];
struct Node{ int l, r, a, b, id;}q[N];
bool cmp(Node a, Node b){
if(pos[a.l] == pos[b.l]) return a.r < b.r;
return pos[a.l] < pos[b.l];
}
void Add(int x){ if(cnt[x] == 0) val[x]++, sum[pos[x]]++; cnt[x]++;}
void Del(int x){ if(cnt[x] == 1) val[x]--, sum[pos[x]]--; cnt[x]--;}
int Qu(int l, int r){
int ans = 0;
if(pos[r] - pos[l] <= 1){
for(int i=l; i<=r; i++) ans += val[i];
}
else{
for(int i=pos[l]+1; i<=pos[r]-1; i++) ans += sum[i];
for(int i=l; i<=R[pos[l]]; i++) ans += val[i];
for(int i=L[pos[r]]; i<=r; i++) ans += val[i];
} return ans;
}
int main(){
n = read(); m = read();
for(int i=1; i<=n; i++) a[i] = read();
int S = sqrt(n);
for(int i=1; i<=n; i++) pos[i] = (i-1)/S + 1;
for(int i=1; i<=n; i+=S) L[++res] = i, R[res] = i+S-1; R[res] = n;
for(int i=1; i<=m; i++){
q[i] = (Node){read(), read(), read(), read(), i};
} sort(q+1, q+m+1, cmp);
int l = 1, r = 0;
for(int i=1; i<=m; i++){
while(r < q[i].r) Add(a[++r]); while(r > q[i].r) Del(a[r--]);
while(l < q[i].l) Del(a[l++]); while(l > q[i].l) Add(a[--l]);
ans[q[i].id] = Qu(q[i].a, q[i].b);
} for(int i=1; i<=m; i++) printf("%d\n", ans[i]);
return 0;
}
例3: P3246 [HNOI2016]序列
考虑[l, r] 到 [l, r+1]的变化, 多出来的就是 [l, r+1], [l+1, r+1], [l+2, r+1] ... [r+1, r+1] 这些区间的最小值的和
不妨设 p 为l, r+1之间最小值的位置, 那么(p - l + 1) * a[p] 就是 [l, r+1]---[p, r+1] 这些区间的贡献
不妨设 p2 为 p+1, r+1之间的最小值的位置, 那么(p2 - p) * a[p2] 就是 [p+1, r+1] --- [p2, r+1] 的贡献
然后 p3 , p4 知道r+1是最小的
我们考虑预处理这个过程, 首先用单调栈处理前后第一个小于它的位置pre, 与suf
fl[i] 表示从1 -- i 的用上述过程算出来的值 fl[i] = fl[pre_i] + (i - pre_i) * a[i]
然后p+1 到 r+1 的贡献就是 fl[r+1] - fl[p+1] 因为r+1 一定是从 p+1转移了几次过来的, 所以可以相减
然后预处理fr[i]表示从 i--n的, 转移时套一个st表查最小就可以了
#include<bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long ll;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, m, a[N], sta[N];
int pre[N], suf[N];
int st[N][20], lg[N];
ll ans[N], Sum, fl[N], fr[N], pos[N];
struct Node{int l, r, id;}q[N];
bool cmp(Node a, Node b){
if(pos[a.l] == pos[b.l]) return a.r < b.r;
return pos[a.l] < pos[b.l];
}
int RMQ(int l, int r){
int x = lg[r - l + 1];
if(a[st[l][x]] < a[st[r-(1<<x)+1][x]]) return st[l][x];
else return st[r-(1<<x)+1][x];
}
void L_Add(int L, int R){
int p = RMQ(L, R); Sum += fl[L] - fl[p] + 1ll * (R - p + 1) * a[p];
}
void R_Add(int L, int R){
int p = RMQ(L, R); Sum += fr[R] - fr[p] + 1ll * (p - L + 1) * a[p];
}
void L_Del(int L, int R){
int p = RMQ(L, R); Sum -= fl[L] - fl[p] + 1ll * (R - p + 1) * a[p];
}
void R_Del(int L, int R){
int p = RMQ(L, R); Sum -= fr[R] - fr[p] + 1ll * (p - L + 1) * a[p];
}
int main(){
n = read(), m = read();
for(int i=1; i<=n; i++) a[i] = read(), st[i][0] = i;
for(int i=2; i<=n; i++) lg[i] = lg[i/2] + 1;
for(int i=1; (1<<i) <= n; i++)
for(int j=1; j+(1<<i)-1<=n; j++){
if(a[st[j][i-1]] < a[st[j+(1<<(i-1))][i-1]]) st[j][i] = st[j][i-1];
else st[j][i] = st[j+(1<<(i-1))][i-1];
}
int top = 0;
for(int i=1; i<=n; i++){
while(top && a[i] < a[sta[top]]){
suf[sta[top]] = i; top--;
} pre[i] = sta[top]; sta[++top] = i;
} while(top) suf[sta[top--]] = n+1;
for(int i=1; i<=n; i++) fr[i] = fr[pre[i]] + 1ll * (i - pre[i]) * a[i];
for(int i=n; i>=1; i--) fl[i] = fl[suf[i]] + 1ll * (suf[i] - i) * a[i];
int S = sqrt(n);
for(int i=1; i<=n; i++) pos[i] = (i-1) / S + 1;
for(int i=1; i<=m; i++) q[i] = (Node){read(), read(), i};
sort(q+1, q+m+1, cmp);
int l = 1, r = 0;
for(int i=1; i<=m; i++){
while(r < q[i].r) R_Add(l, ++r); while(r > q[i].r) R_Del(l, r--);
while(l < q[i].l) L_Del(l++, r); while(l > q[i].l) L_Add(--l, r);
ans[q[i].id] = Sum;
}
for(int i=1; i<=m; i++) printf("%lld\n", ans[i]);
return 0;
}
待修莫队
开始不是很理解, 其实就是加一个时间戳t, 然后跟着移动就可以了, 复杂度分析有点毒瘤(from scarlyw)
来一道例题
P1903 [国家集训队]数颜色
我们用一个struct 记录每个修改的 pos, pre(原来颜色), nxt(改后颜色)
每个询问记录 l, r, t(正好在它前面的修改的时间戳), id
排序按 pos[l] 为第一关键字, pos[r] 为第二关键字, t为第3关键字
l, r 移动同普通莫队, 最后要把当前t 移动到询问的 t, 如果修改在l, r内就修改, 否则不管
#include<bits/stdc++.h>
#define N 500500
#define M 1000050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, m, Qn, Cn, a[N], b[N], pos[N], Sum, cnt[M], ans[N];
struct Quary{int l, r, t, id;}q[N];
struct Change{int x, pre, nxt;}c[N];
bool cmp(Quary a, Quary b){
return (pos[a.l] < pos[b.l]) || (pos[a.l] == pos[b.l] && pos[a.r] < pos[b.r])
|| (pos[a.l] == pos[b.l] && pos[a.r] == pos[b.r] && a.t < b.t);
}
void Add(int x){if(!cnt[x]) Sum++; cnt[x]++;}
void Del(int x){if(cnt[x]==1) Sum--; cnt[x]--;}
int main(){
n = read(), m = read(); int S = pow(n, 2/3);
for(int i=1; i<=n; i++) a[i] = b[i] = read(), pos[i] = (i-1)/S + 1;
while(m--){
char op[3]; scanf("%s", op);
if(op[0] == 'Q') q[++Qn] = (Quary){read(), read(), Cn, Qn};
if(op[0] == 'R'){ int p = read(), v = read();
c[++Cn] = (Change){p, b[p], v}; b[p] = v;
}
} sort(q+1, q+Qn+1, cmp);
int l = 1, r = 0, now = 0;
for(int i=1; i<=Qn; i++){
while(l < q[i].l) Del(a[l++]);
while(l > q[i].l) Add(a[--l]);
while(r < q[i].r) Add(a[++r]);
while(r > q[i].r) Del(a[r--]);
while(now < q[i].t){ ++now;
if(q[i].l <= c[now].x && c[now].x <= q[i].r){
Del(c[now].pre); Add(c[now].nxt);
} a[c[now].x] = c[now].nxt;
}
while(now > q[i].t){
if(q[i].l <= c[now].x && c[now].x <= q[i].r){
Del(c[now].nxt); Add(c[now].pre);
} a[c[now].x] = c[now].pre; now--;
}
ans[q[i].id] = Sum;
} for(int i=1; i<=Qn; i++) printf("%d\n", ans[i]);
return 0;
}
树上莫队
我接触到的有两种写法, 一种是基于树分块的, 一种是基于dfs序的, 我学的是基于dfs序的, 树分块了解一下就好
我们建立出dfs序后(例如 1 2 2 3 4 4 3 1), 记录st[u], ed[u] 表示起始于终止
如果lca(u, v) = u, 那么v在u的子树中, 我们可以令左端点为st[u], 右端点为 st[v](这种情况包涵了lca)
否则, v, u 在不同子树中, 我们令左端点为 ed[u], 右端点为 st[v]
这种方法巧妙就在, 如果一个点不在路径上, 要么压根没有到, 要么到了两次(进去了又出来了)
所以我们记录一个vis, 如果vis为1 就删除, 为0就添加, 最后加上lca就可以了
例1:WOJ1196 苹果树
裸模板
#include<bits/stdc++.h>
#define N 200050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int first[N], nxt[N], to[N], tot;
void add(int x, int y){
nxt[++tot] = first[x], first[x] = tot, to[tot] = y;
}
int n, m, col[N], rt, Bel[N], vis[N], Sum;
int fa[N][20], dep[N], pos[N], st[N], ed[N], sign, cnt[N], ans[N];
struct Node{ int l, r, a, b, id, lca;} q[N];
bool cmp(Node a, Node b){
if(Bel[a.l] == Bel[b.l]) return a.r < b.r;
return Bel[a.l] < Bel[b.l];
}
void dfs(int u, int f){
for(int i=1; i<=18; i++) fa[u][i] = fa[fa[u][i-1]][i-1];
pos[++sign] = u; st[u] = sign;
for(int i=first[u];i;i=nxt[i]){
int t = to[i]; if(t == f) continue;
fa[t][0] = u; dep[t] = dep[u] + 1; dfs(t, u);
} pos[++sign] = u; ed[u] = sign;
}
void calc(int x){
if(cnt[col[x]]) Sum--;
if(!vis[x]) cnt[col[x]]++;
else cnt[col[x]]--;
if(cnt[col[x]]) Sum++; vis[x] ^= 1;
}
int LCA(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
for(int i=18; i>=0; i--)
if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
if(x == y) return x;
for(int i=18; i>=0; i--)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main(){
n = read(), m = read();
for(int i=1; i<=n; i++) col[i] = read();
for(int i=1; i<=n; i++){
int x = read(), y = read();
if(!x || !y) rt = x + y;
else add(x, y), add(y, x);
} dep[rt] = 1; dfs(rt, 0);
int S = sqrt(sign);
for(int i=1; i<=sign; i++) Bel[i] = (i-1)/S + 1;
for(int i=1; i<=m; i++){
int u = read(), v = read(), a = read(), b = read();
if(st[u] > st[v]) swap(u, v);
int lca = LCA(u, v);
if(u == lca) q[i] = (Node){ st[u]+1, st[v], a, b, i, lca};
else q[i] = (Node){ ed[u], st[v], a, b, i, lca};
} sort(q+1, q+m+1, cmp);
int l = 1, r = 0;
for(int i=1; i<=m; i++){
while(r < q[i].r) calc(pos[++r]); while(r > q[i].r) calc(pos[r--]);
while(l < q[i].l) calc(pos[l++]); while(l > q[i].l) calc(pos[--l]);
calc(q[i].lca);
if(cnt[q[i].a] && cnt[q[i].b] && q[i].a != q[i].b) ans[q[i].id] = Sum-1;
else ans[q[i].id] = Sum;
calc(q[i].lca);
} for(int i=1; i<=m; i++) printf("%d\n", ans[i]);
return 0;
}
例2:P4074 [WC2013]糖果公园
也是裸模板, 细节有点多
#include<bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long ll;
#define int long long
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, m, Q;
int v[N], w[N], b[N], col[N], vis[N];
int first[N], nxt[N], to[N], tot;
void add(int x, int y){
nxt[++tot] = first[x], first[x] = tot, to[tot] = y;
}
int fa[N][20], dep[N], pos[N], st[N], ed[N], sign;
void dfs(int u, int f){
for(int i=1; i<=18; i++) fa[u][i] = fa[fa[u][i-1]][i-1];
st[u] = ++sign; pos[sign] = u;
for(int i=first[u];i;i=nxt[i]){
int t = to[i]; if(t == f) continue;
fa[t][0] = u; dep[t] = dep[u] + 1; dfs(t, u);
} ed[u] = ++sign; pos[sign] = u;
}
int LCA(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
for(int i=18; i>=0; i--)
if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
if(x == y) return x;
for(int i=18; i>=0; i--)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int Bel[N], Cn, Qn, cnt[N];
struct Node{ int l, r, t, lca, id;} q[N];
struct Modify{ int x, pre, nxt;} c[N];
bool cmp(Node a, Node b){
return (Bel[a.l] < Bel[b.l]) || (Bel[a.l] == Bel[b.l] && Bel[a.r] < Bel[b.r])
|| (Bel[a.l] == Bel[b.l] && Bel[a.r] == Bel[b.r] && a.t < b.t);
}
ll ans[N], Sum;
void calc(int x){
if(cnt[col[x]]) Sum -= v[col[x]] * w[cnt[col[x]]];
if(vis[x]) cnt[col[x]]--; else cnt[col[x]]++;
if(cnt[col[x]]) Sum += v[col[x]] * w[cnt[col[x]]];
vis[x] ^= 1;
}
signed main(){
n = read(), m = read(), Q = read();
for(int i=1; i<=m; i++) v[i] = read();
for(int i=1; i<=n; i++) w[i] = read(), w[i] += w[i-1];
for(int i=1; i<n; i++){
int x = read(), y = read();
add(x, y); add(y, x);
} dep[1] = 1; dfs(1, 0);
for(int i=1; i<=n; i++) col[i] = b[i] = read();
int S = pow(sign, 2.0/3.0);
for(int i=1; i<=sign; i++) Bel[i] = (i-1)/S + 1;
for(int i=1; i<=Q; i++){
int op = read(), x = read(), y = read();
if(op == 0) c[++Cn] = (Modify){x, b[x], y}, b[x] = y;
if(op == 1){
int lca = LCA(x, y);
if(st[x] > st[y]) swap(x, y);
if(x == lca) q[++Qn] = (Node){st[x], st[y], Cn, 0, Qn};
else q[++Qn] = (Node){ed[x], st[y], Cn, lca, Qn};
}
} sort(q+1, q+Qn+1, cmp);
int l = 1, r = 0, now = 0;
for(int i=1; i<=Qn; i++){
while(l < q[i].l) calc(pos[l++]); while(l > q[i].l) calc(pos[--l]);
while(r < q[i].r) calc(pos[++r]); while(r > q[i].r) calc(pos[r--]);
if(q[i].lca) calc(q[i].lca);
while(now < q[i].t){
now++;
if(vis[c[now].x]){
calc(c[now].x), col[c[now].x] = c[now].nxt, calc(c[now].x);
} col[c[now].x] = c[now].nxt;
}
while(now > q[i].t){
if(vis[c[now].x]){
calc(c[now].x), col[c[now].x] = c[now].pre, calc(c[now].x);
} col[c[now].x] = c[now].pre; now--;
}
ans[q[i].id] = Sum;
if(q[i].lca) calc(q[i].lca);
}
for(int i=1; i<=Qn; i++) printf("%lld\n", ans[i]);
return 0;
}