目录
数据结构刷题记录
AT1219 歴史の研究 - 莫队
- 简单的回滚莫队,考虑到维护最大值,加操作好做而减操作难
- 还可以将每个数可能的贡献算出,即对于一个数x,贡献为x1,x2,...,x*y(y为在整个序列中x出现个数),离散化后,用值域分块维护,只用普通莫队即可
- 下面是回滚莫队做法的代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
int n, m, len;
long long a[MAXN], b[MAXN], dr[MAXN];
int block, bn, B[MAXN];
long long ans, cm[MAXN], Ans[MAXN], ct[MAXN];
int s[MAXN], st;
struct question
{
int l, r, id;
}ask[MAXN];
inline bool cmp(const question &a, const question &b)
{
if(B[a.l] ^ B[b.l]) return B[a.l] < B[b.l];
return a.r < b.r;
}
inline long long max(const long long &x, const long long &y)
{
return x > y ? x : y;
}
inline long long min(const long long &x, const long long &y)
{
return x < y ? x : y;
}
inline void discrete()
{
len = 0;
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++)
{
if(i == 1 || b[i] != b[i - 1])
dr[++len] = b[i];
}
}
inline long long calc(int l, int r)
{
long long res = 0;
for (int i = l; i <= r; i++) cm[a[i]] = 0;
for (int i = l; i <= r; i++)
{
cm[a[i]]++;
res = max(cm[a[i]] * dr[a[i]], res);
}
return res;
}
int main()
{
scanf("%d %d", &n, &m); block = pow(n, 1.0 / 2.0);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]), b[i] = a[i];
discrete();
for (int i = 1; i <= n; i++) a[i] = lower_bound(dr + 1, dr + len + 1, a[i]) - dr;
for (int i = 1; i <= m; i++)
{
scanf("%d %d", &ask[i].l, &ask[i].r);
ask[i].id = i;
}
for (int i = 1; i <= n; i++) B[i] = (i - 1) / block + 1;
sort(ask + 1, ask + m + 1, cmp);
bn = B[n];
for (int i = 1, j = 1; i <= bn; i++)
{
int br = min(n, i * block), l = br + 1, r = br;
st = 0;//s数组维护出现了那些数
ans = 0;
for (; B[ask[j].l] == i; j++)
{
if(B[ask[j].r] == i)
{
Ans[ask[j].id] = calc(ask[j].l, ask[j].r);
continue;
}
while(r < ask[j].r)
{
r++;
if(!ct[a[r]]) s[++st] = a[r];
ct[a[r]]++; ans = max(ans, ct[a[r]] * dr[a[r]]);
}
long long tmp = ans;
while(l > ask[j].l)
{
l--;
ct[a[l]]++; tmp = max(tmp, ct[a[l]] * dr[a[l]]);
}
Ans[ask[j].id] = tmp;
while(l <= br)
{
ct[a[l]]--;
l++;
}
}
for (int i = 1; i <= st; i++) ct[s[i]] = 0;
}
for (int i = 1; i <= m; i++) printf("%lld
", Ans[i]);
return 0;
}
[国家集训队]旅游 - 树剖
- 简单的树剖题(码量有点大)
- 考虑点权变为边权后计算的位置即可(记录每条边的位置)
- 利用线段树懒标记解决区间问题
- 区间取相反数有点像区间翻转,打个标记就行了
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5000010;
int n, m;
int head[MAXN], nxt[MAXN << 1], v[MAXN << 1], w[MAXN << 1], b[MAXN << 1], cnt;
int son[MAXN], size[MAXN], d[MAXN], wo[MAXN], fa[MAXN], top[MAXN], sv[MAXN], ff[MAXN], totw;
int val[MAXN];
struct SegmentTree
{
int l, r, sum, res, ma, mi;
#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum
#define res(x) t[x].res
#define ma(x) t[x].ma
#define mi(x) t[x].mi
}t[MAXN << 2];
void add(int x, int y, int z, int i)
{
nxt[++cnt] = head[x]; head[x] = cnt; v[cnt] = y; w[cnt] = z; b[cnt] = i;
}
void dfs1(int now, int f, int de)
{
size[now] = 1; fa[now] = f; d[now] = de;
int maxx = -1;
for (int i = head[now]; i; i = nxt[i])
{
if(v[i] == f) continue;
dfs1(v[i], now, de + 1);
if(size[v[i]] > maxx) maxx = size[v[i]], son[now] = v[i], sv[now] = i;
size[now] += size[v[i]];
}
}
void dfs2(int now, int topf, int p)
{
top[now] = topf; ff[now] = p;
if(now ^ 1) wo[b[p]] = ++totw, val[totw] = w[p];
if(!son[now]) return;
dfs2(son[now], topf, sv[now]);
for (int i = head[now]; i; i = nxt[i])
{
if(v[i] == fa[now] || v[i] == son[now]) continue;
dfs2(v[i], v[i], i);
}
}
void maintain(int p)
{
sum(p) = sum(p << 1) + sum(p << 1 | 1);
ma(p) = max(ma(p << 1), ma(p << 1 | 1));
mi(p) = min(mi(p << 1), mi(p << 1 | 1));
}
void build(int p, int l, int r)
{
l(p) = l; r(p) = r;
if(l == r)
{
sum(p) = mi(p) = ma(p) = val[r];
res(p) = 0;
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
maintain(p);
}
void pushdown(int p)
{
if(res(p))
{
sum(p << 1) = -sum(p << 1);
sum(p << 1 | 1) = -sum(p << 1 | 1);
swap(mi(p << 1), ma(p << 1));
mi(p << 1) = -mi(p << 1);
ma(p << 1) = -ma(p << 1);
swap(mi(p << 1 | 1), ma(p << 1 | 1));
mi(p << 1 | 1) = -mi(p << 1 | 1);
ma(p << 1 | 1) = -ma(p << 1 | 1);
res(p) ^= 1;
res(p << 1) ^= 1;
res(p << 1 | 1) ^= 1;
}
}
int askma(int p, int l, int r)
{
if(l(p) >= l && r(p) <= r) return ma(p);
pushdown(p);
int ans = -100000000;
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) ans = max(ans, askma(p << 1, l, r));
if(r > mid) ans = max(ans, askma(p << 1 | 1, l, r));
return ans;
}
int asksu(int p, int l, int r)
{
if(l(p) >= l && r(p) <= r) return sum(p);
pushdown(p);
int ans = 0;
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) ans += asksu(p << 1, l, r);
if(r > mid) ans += asksu(p << 1 | 1, l ,r);
return ans;
}
int askmi(int p, int l, int r)
{
if(l(p) >= l && r(p) <= r)
{
return mi(p);
}
pushdown(p);
int ans = 100000000;
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) ans = min(ans, askmi(p << 1, l, r));
if(r > mid) ans = min(ans, askmi(p << 1 | 1, l, r));
return ans;
}
void modify(int p, int l, int r)
{
if(l(p) >= l && r(p) <= r)
{
res(p) ^= 1;
sum(p) = -sum(p);
int tmp = ma(p);
ma(p) = -mi(p);
mi(p) = -tmp;
return;
}
pushdown(p);
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) modify(p << 1, l, r);
if(r > mid) modify(p << 1 | 1, l, r);
maintain(p);
}
void change(int p, int x, int dt)
{
if(l(p) == r(p))
{
sum(p) = ma(p) = mi(p) = dt;
return;
}
pushdown(p);
int mid = (l(p) + r(p)) >> 1;
if(x <= mid) change(p << 1, x, dt);
else change(p << 1 | 1, x, dt);
maintain(p);
}
void mo(int x, int y)
{
while(top[x] != top[y])
{
if(d[top[x]] < d[top[y]]) swap(x, y);
modify(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]);
x = fa[top[x]];
}
if(x == y) return;
if(d[x] > d[y]) swap(x, y);
modify(1, wo[b[sv[x]]], wo[b[ff[y]]]);
return;
}
int mam(int x, int y)
{
int ans = -100000000;
while(top[x] != top[y])
{
if(d[top[x]] < d[top[y]]) swap(x, y);
ans = max(ans, askma(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]));
x = fa[top[x]];
}
if(x == y) return ans;
if(d[x] > d[y]) swap(x, y);
ans = max(ans, askma(1, wo[b[sv[x]]], wo[b[ff[y]]]));
return ans;
}
int mim(int x, int y)
{
int ans = 100000000;
while(top[x] != top[y])
{
if(d[top[x]] < d[top[y]]) swap(x, y);
ans = min(ans, askmi(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]));
x = fa[top[x]];
}
if(x == y) return ans;
if(d[x] > d[y]) swap(x, y);
ans = min(ans, askmi(1, wo[b[sv[x]]], wo[b[ff[y]]]));
return ans;
}
int sus(int x, int y)
{
int ans = 0;
while(top[x] != top[y])
{
if(d[top[x]] < d[top[y]]) swap(x, y);
ans += asksu(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]);
x = fa[top[x]];
}
if(x == y) return ans;
if(d[x] > d[y]) swap(x, y);
ans += asksu(1, wo[b[sv[x]]], wo[b[ff[y]]]);
return ans;
}
int main()
{
// freopen("inp", "r", stdin);
// freopen("out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i < n; i++)
{
int x, y, z;
scanf("%d %d %d", &x, &y, &z); x++, y++;
add(x, y, z, i);
add(y, x, z, i);
}
dfs1(1, 0, 1);
dfs2(1, 1, 1);
build(1, 1, totw);
scanf("%d", &m);
for (int i = 1; i <= m; i++)
{
char opt[10];
scanf("%s", opt + 1);
if(opt[1] == 'C')
{
int x, y;
scanf("%d %d", &x, &y);
change(1, wo[x], y);
}
else if(opt[1] == 'N')
{
int x, y;
scanf("%d %d", &x, &y);x++, y++;
mo(x, y);
}
else if(opt[1] == 'S')
{
int x, y;
scanf("%d %d", &x, &y);x++, y++;
printf("%d
", sus(x, y));
}
else if(opt[2] == 'A')
{
int x, y;
scanf("%d %d", &x, &y);x++, y++;
printf("%d
", mam(x, y));
}
else
{
int x, y;
scanf("%d %d", &x, &y);x++, y++;
printf("%d
", mim(x, y));
}
}
return 0;
}
[NOI Online #1 提高组]冒泡排序 - 树状数组、逆序对
- 比较有思维的一道题
- 首先考虑冒泡排序的本质是通过n轮检查邻项是否逆序,并通过邻项的交换减少逆序对个数从而使序列有序
- 我们用(fm[i])表示第i个数前面有几个数比他大,总逆序对个数为(sum_{i=1}^{n}{fm[i]})
- 考虑一轮冒泡排序,每一位置的(c[i])必定会减一,且仅会减一
- 故k轮后(Ans = sum_{i=1}^{n}{[fm[i]geq{k + 1}] imes(fm[i] - k)})
- 我们求出了fm数组后,用两个树状数组在fm[i]值域上分别维护前缀个数和前缀(sum{fm[i]})
- 注意当k >= n 时特判逆序对个数必定为0(n-1轮就有序了)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5000010;
int n, m, a[MAXN];
long long c[MAXN], f[MAXN];
int fm[MAXN];
inline int lowbit(int x) { return x & (-x); }
inline long long ask(int x)
{
long long ans = 0;
while(x)
{
ans += c[x];
x -= lowbit(x);
}
return ans;
}
inline void add(int x, int dt)
{
if(x == 0) return;
while(x <= n)
{
c[x] += dt;
x += lowbit(x);
}
}
inline long long ask2(int x)
{
long long ans = 0;
while(x)
{
ans += f[x];
x -= lowbit(x);
}
return ans;
}
inline void add2(int x, int dt)
{
if(x == 0) return;
while(x <= n)
{
f[x] += dt;
x += lowbit(x);
}
}
int main()
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++)
{
fm[i] = i - 1 - ask(a[i]);
add(a[i], 1);
}
for (int i = 1; i <= n; i++) c[i] = 0;
for (int i = 1; i <= n; i++)
{
add(fm[i], 1);
add2(fm[i], fm[i]);
}
for (int i = 1; i <= m; i++)
{
int opt, x;
scanf("%d %d", &opt, &x);
if(opt == 1)
{
add(fm[x], -1); add2(fm[x], -fm[x]);
add(fm[x + 1], -1); add2(fm[x + 1], -fm[x + 1]);
if(a[x] > a[x + 1]) fm[x + 1]--;
else fm[x]++;
swap(fm[x], fm[x + 1]);
swap(a[x], a[x + 1]);
add(fm[x], 1); add2(fm[x], fm[x]);
add(fm[x + 1], 1); add2(fm[x + 1], fm[x + 1]);
}
else
{
if(x >= n) printf("0
");
else
{
long long si = ask2(n) - ask2(x), ti = ask(n) - ask(x);
printf("%lld
", si - ti * x);
}
}
}
return 0;
}
[HAOI2007]理想的正方形 - 单调队列
- 考虑我们如果可以求出每一个位置((i, j))为右下角的(n imes{n})的矩形中的最大值和最小值,即可求出答案
- 两次单调队列先做一行,再在每一列上根据第一次做的答案,求出矩形上的最值
- 即一次做到点到线上的维护,第二次做到线到面上的维护
#include <bits/stdc++.h>
using namespace std;
const int SIZE = 1010;
int a, b, n;
int g[SIZE][SIZE];
int ma[SIZE][SIZE], mi[SIZE][SIZE], maa[SIZE][SIZE], mii[SIZE][SIZE];
int q[SIZE], l = 1, r = 0;
int main()
{
scanf("%d %d %d", &a, &b, &n);
for (int i = 1; i <= a; i++)
for (int j = 1; j <= b; j++)
scanf("%d", &g[i][j]);
for (int i = 1; i <= a; i++)
{
l = 1; r = 0;
for (int j = 1; j <= b; j++)
{
while(l <= r && j - q[l] + 1 > n) l++;
while(l <= r && g[i][j] >= g[i][q[r]]) r--;
q[++r] = j;
ma[i][j] = g[i][q[l]];
}
}
/*
for (int i = 1; i <= a; i++)
{
for (int j = 1; j <= b; j++)
cout << ma[i][j] << " ";
cout << endl;
}
*/
for (int i = 1; i <= a; i++)
{
l = 1; r = 0;
for (int j = 1; j <= b; j++)
{
while(l <= r && j - q[l] + 1 > n) l++;
while(l <= r && g[i][j] <= g[i][q[r]]) r--;
q[++r] = j;
mi[i][j] = g[i][q[l]];
}
}
for (int i = 1; i <= b; i++)
{
l = 1; r = 0;
for (int j = 1; j <= a; j++)
{
while(l <= r && j - q[l] + 1 > n) l++;
while(l <= r && ma[j][i] >= ma[q[r]][i]) r--;
q[++r] = j;
maa[j][i] = ma[q[l]][i];
}
}
for (int i = 1; i <= b; i++)
{
l = 1; r = 0;
for (int j = 1; j <= a; j++)
{
while(l <= r && j - q[l] + 1 > n) l++;
while(l <= r && mi[j][i] <= mi[q[r]][i]) r--;
q[++r] = j;
mii[j][i] = mi[q[l]][i];
}
}
int ans = 1000000000;
for (int i = n; i <= a; i++)
for (int j = n; j <= b; j++)
{
ans = min(ans, maa[i][j] - mii[i][j]);
}
printf("%d", ans);
return 0;
}
[HEOI2012]采花
- 首先(10^5)的数据可以用莫队做
- 更高效的做法是用树状数组做,与HH的项链相似
- 考虑先预处理出每个位置的下一个相同颜色的位置,同时在每个颜色第二次出现的位置先加上一
- 将询问离线做,按左端点排序,则每次递增删除当前未在左区间的点的贡献,即将此位置的下个同颜色的位置贡献减去,下下个位置贡献加上(此中颜色当前最左边位置少了一个)
- 再用树状数组计算前缀和,作差即可
- (Ans = sum(r) - sum(l - 1))
/*
#include <bits/stdc++.h>
using namespace std;
int read()
{
int a = 0, f = 1; char c = getchar();
while(c > '9' || c < '0') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {a = a * 10 + c - '0'; c = getchar();}
return a * f;
}
const int MAXN = 2e6 + 14;
int n, m, t, xl, xr, block;
int ans;
int a[MAXN];
int cnt[MAXN];
int Ans[MAXN];
struct question
{
int l, r, bo, id;
#define l(x) ask[x].l
#define r(x) ask[x].r
#define bo(x) ask[x].bo
#define id(x) ask[x].id
} ask[MAXN];
bool cmp(question a, question b)
{
return a.bo ^ b.bo ? a.bo < b.bo : a.bo % 2 == 1 ? a.r < b.r : a.r > b.r;
}
void add(int x)
{
cnt[a[x]]++;
if(cnt[a[x]] == 2) ans++;
}
void del(int x)
{
cnt[a[x]]--;
if(cnt[a[x]] == 1) ans--;
}
int main() {
n = read(); t = read(); m = read(); block = sqrt(n);
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= m; i++) l(i) = read(), r(i) = read(), bo(i) = l(i) / block, id(i) = i;
sort(ask + 1, ask + m + 1, cmp);
for (int i = 1; i <= m; i++)
{
while(xr > r(i)) del(xr--);
while(xr < r(i)) add(++xr);
while(xl < l(i)) del(xl++);
while(xl > l(i)) add(--xl);
Ans[id(i)] = ans;
}
for (int i = 1; i <= m; i++)
{
printf("%d
", Ans[i]);
}
return 0;
}
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000010;
inline int read()
{
int a = 0, f = 1; char c = getchar();
while(c > '9' || c < '0') { if(c == '-') f = -1; c = getchar(); }
while(c >= '0' && c <= '9') { a = a * 10 + c - '0'; c = getchar(); }
return a * f;
}
int n, m, t;
int a[MAXN], pre[MAXN], Ans[MAXN], la[MAXN], cnt[MAXN];
struct node
{
int l, r, id;
}ask[MAXN];
inline bool cmp(const node &a, const node &b)
{
return a.l < b.l;
}
int c[MAXN];
inline int lowbit(int x)
{
return x & (-x);
}
inline void add(int x, int dt)
{
while(x <= n)
{
c[x] += dt;
x += lowbit(x);
}
}
inline int sum(int x)
{
int ans = 0;
while(x)
{
ans += c[x];
x -= lowbit(x);
}
return ans;
}
int main()
{
n = read(); t = read(); m = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= m; i++) ask[i].l = read(), ask[i].r = read(), ask[i].id = i;
for (int i = n; i >= 1; i--)
{
if(pre[a[i]]) la[i] = pre[a[i]];
pre[a[i]] = i;
}
for (int i = 1; i <= t; i++)
if(la[pre[i]])
add(la[pre[i]], 1);
sort(ask + 1, ask + m + 1, cmp);
int nl = 1;
for (int i = 1; i <= m; i++)
{
while(nl < ask[i].l)
{
if(la[nl]) add(la[nl], -1);
if(la[la[nl]]) add(la[la[nl]], 1);
nl++;
}
Ans[ask[i].id] = sum(ask[i].r) - sum(ask[i].l - 1);
}
for (int i = 1; i <= m; i++)
printf("%d
", Ans[i]);
return 0;
}