2020寒假-莫队小结
莫队算法笔记
好题
HH的项链
BZOJ 1878: [SDOI2009]HH的项链
每次移动左右指针的时候维护每种项链出现次数的\(cnt\)数组,当从无到有和从有到无的时候统计答案
洛谷卡这道题,上B站交
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
return cnt * f;
}
const int N = 1000010;
int n, m, tot;
int col[N], ans[N], pos[N], cnt[N];
struct node {
int l, r;
int id;
}Q[N];
bool cmp (node a, node b) {
return pos[a.l] == pos[b.l] ? a.r < b.r : pos[a.l] < pos[b.l];
}
void add(int p) {
if (++cnt[col[p]] == 1) ++tot;
}
void del(int p) {
if (--cnt[col[p]] == 0) --tot;
}
int main() {
n = read();
for (register int i = 1; i <= n; ++i) col[i] = read();
int base = sqrt(n);
for (register int i = 1; i <= n; ++i) pos[i] = (i - 1) / base + 1;
m = read();
for (register int i = 1; i <= m; ++i) {
Q[i].l = read(), Q[i].r = read();
Q[i].id = i;
} sort (Q + 1, Q + m + 1, cmp);
int l = 1, r = 0;
for (register int i = 1; i <= m; ++i) {
while (l > Q[i].l) add(--l);
while (r < Q[i].r) add(++r);
while (l < Q[i].l) del(l++);
while (r > Q[i].r) del(r--);
ans[Q[i].id] = tot;
}
for (register int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
return 0;
}
小Z的袜子
答案是\(\frac{合法方案}{总方案}\),总方案为\((r - l + 1)(r - l)\),合法方案考虑钦定选某一色的袜子,那么剩下一只也要在这个颜色里选,为\(\sum\limits_{i = 1}^{m}cnt[i] * (cnt[i] - 1)\)
总方案不用维护,合法方案每次挪动左右指针时,先把变化前它的贡献减掉,然后进行增减,最后把改变后的贡献加上
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
return cnt * f;
}
typedef long long ll;
const int N = 50010;
int n, m;
ll ans;
int col[N], pos[N];
ll ans1[N], ans2[N], cnt[N];
struct node {
int l, r;
int id;
}Q[N];
ll gcd(ll a, ll b){return b ? gcd(b, a % b) : a;}
bool cmp(node a, node b) {
return pos[a.l] != pos[b.l] ? pos[a.l] < pos[b.l] : a.r < b.r;
}
void modify(int p, ll k) {
ans -= cnt[col[p]] * (cnt[col[p]] - 1);
cnt[col[p]] += k;
ans += cnt[col[p]] * (cnt[col[p]] - 1);
}
signed main() {
n = read(), m = read();
for (register int i = 1; i <= n; ++i) col[i] = read();
for (register int i = 1; i <= m; ++i) {
Q[i].l = read(), Q[i].r = read();
Q[i].id = i;
}
int base = sqrt(n);
for (register int i = 1; i <= n; ++i) pos[i] = ((i - 1) / base) + 1;
sort (Q + 1, Q + m + 1, cmp);
// int l = 0, r = 0;
int l = 1, r = 1;
++cnt[col[1]];
for (register int i = 1; i <= m; ++i) {
if (Q[i].l == Q[i].r) {ans1[Q[i].id] = 0, ans2[Q[i].id] = 1; continue;}
while (l > Q[i].l) modify(--l, 1);
while (r < Q[i].r) modify(++r, 1);
while (l < Q[i].l) modify(l++, -1);
while (r > Q[i].r) modify(r--, -1);
ll tot = (ll)(Q[i].r - Q[i].l + 1) * (Q[i].r - Q[i].l);
ans1[Q[i].id] = ans;
ans2[Q[i].id] = tot;
ll G = gcd(ans1[Q[i].id], ans2[Q[i].id]);
ans1[Q[i].id] /= G, ans2[Q[i].id] /= G;
}
for (register int i = 1; i <= m; ++i) {
printf("%lld/%lld\n", ans1[i], ans2[i]);
}
return 0;
}
Mato的文件管理
考虑每次挪动的时候改变的逆序对数量是挪动时相关的这个元素形成的逆序对数量
树状数组维护一下
#include<bits/stdc++.h>
#define lowbit(x) (x & (-x))
using namespace std;
inline int read() {
int cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) +(c ^ 48); c = getchar();}
return cnt * f;
}
const int N = (50000 + 10);
typedef long long ll;
int n, q, L, R, res;
int a[N], b[N], pos[N], ans[N];
struct node {
int l, r;
int id;
}Q[N];
bool cmp (node a, node b) {
return pos[a.l] == pos[b.l] ? a.r < b.r : pos[a.l] < pos[b.l];
}
struct node2 {
ll BIT[N];
void modify(int x, ll d) {for (; x <= n; x += lowbit(x)) {BIT[x] += d;}}
ll query(int x) {ll ans = 0; for (; x; x -= lowbit(x)) ans += BIT[x]; return ans;}
}BIT;
int main() {
n = read();
for (register int i = 1; i <= n; ++i) a[i] = b[i] = read();
sort (b + 1, b + n + 1);
int p = unique(b + 1, b + n + 1) - b - 1;
for (register int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + p + 1, a[i]) - b;
q = read();
int base = sqrt(n);
for (register int i = 1; i <= n; ++i) pos[i] = (i - 1) / base + 1;
for (register int i = 1; i <= q; ++i) Q[i].l = read(), Q[i].r = read(), Q[i].id = i;
sort (Q + 1, Q + q + 1, cmp);
L = 1, R = 0;
for (register int i = 1; i <= q; ++i) {
while (L > Q[i].l) --L, BIT.modify(a[L], 1), res += BIT.query(a[L] - 1);
while (R < Q[i].r) ++R, BIT.modify(a[R], 1), res += (R - L + 1 - BIT.query(a[R]));
while (L < Q[i].l) BIT.modify(a[L], -1), res -= BIT.query(a[L] - 1), ++L;
while (R > Q[i].r) BIT.modify(a[R], -1), res -= (R - L - BIT.query(a[R])), --R;
ans[Q[i].id] = res;
}
for (register int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
return 0;
}
Gty的二逼妹子序列
考虑题目实际是两层询问,第一层询问原序列,第二层询问权值序列(桶)
对于第二层有两种维护方式
-
带log的
考虑先对于每种权值维护一个桶,从无到有和从有到无时分别插入1和-1的增量到权值线段树中 查询为常规查询
修改和查询的复杂度都带log 总时间复杂度\(O(n*\sqrt n*logn)\) -
不带log的
观察到实际上修改和查询的操作次数不是一个级别的,修改在每次挪动指针时都要修改,一共修改\(n*\sqrt n\)次
而查询是在挪动指针结束之后才操作的,一共查询\(n\)次
考虑对第二层询问上一个修改复杂度小,查询复杂度大的算法,想到对维护的桶进行分块
分块的修改是\(O(1)\)的,而查询是\(\sqrt n\)的
总时间复杂度是\(O(n * \sqrt n)\)的,虽然带个常数
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
return cnt * f;
}
typedef long long ll;
const int N = 100010;
int n, m;
int a[N], col[N], pos1[N], pos2[N], cnt[N], L[1010], R[1010], sum[1010], ans[N * 10];
void prework() {
int base = sqrt(n);
// for (register int i = 1; i <= n; ++i) pos2[i] = (i - 1) / base + 1;
for (register int i = 1; i <= base; ++i) {
L[i] = R[i - 1] + 1;
R[i] = i * base;
}
if (R[base] < n) {
++base;
L[base] = R[base - 1] + 1;
R[base] = n;
}
for (register int i = 1; i <= base; ++i)
for (register int j = L[i]; j <= R[i]; ++j) pos2[j] = i;
}
struct node {
int l, r, a, b;
int id;
}Q[N * 10];
bool cmp (node a, node b) {
return pos1[a.l] == pos1[b.l] ? a.r < b.r : pos1[a.l] < pos1[b.l];
}
void add(int p) {
if (++cnt[col[p]] == 1) ++sum[pos2[col[p]]];
}
void del(int p) {
if (--cnt[col[p]] == 0) --sum[pos2[col[p]]];
}
int query(int l, int r) {
ll tot = 0;
int p = pos2[l], q = pos2[r];
if (p == q) {
for (register int i = l; i <= r; ++i) if (cnt[i]) ++tot;
return tot;
}
for (register int i = p + 1; i <= q - 1; ++i) tot += sum[i];
for (register int i = l; i <= R[p]; ++i) if (cnt[i]) ++tot;
for (register int i = L[q]; i <= r; ++i) if (cnt[i]) ++tot;
return tot;
}
int main() {
n = read(), m = read();
for (register int i = 1; i <= n; ++i) col[i] = read();
int base = sqrt(n);
for (register int i = 1; i <= n; ++i) pos1[i] = ((i - 1) / base) + 1;
for (register int i = 1; i <= m; ++i) {
Q[i].l = read(), Q[i].r = read(), Q[i].a = read(), Q[i].b = read();
Q[i].id = i;
} sort (Q + 1, Q + m + 1, cmp);
prework();
int l = 1, r = 0;
// ++cnt[col[1]];
for (register int i = 1; i <= m; ++i) {
while (l > Q[i].l) add(--l);
while (r < Q[i].r) add(++r);
while (l < Q[i].l) del(l++);
while (r > Q[i].r) del(r--);
ans[Q[i].id] = query(Q[i].a, Q[i].b);
}
for (register int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
return 0;
}
HNOI2016 序列
奇怪做法一大堆的题,有线段树做法,可翻luogu题解
这里直接贴之前写的莫队题解