树状数组 好题整理
树状数组 好题整理
[SDOI2009] HH的项链
离线询问后,按右端点升序排序,考虑建立一个树状数组,只包含 0/1,把含每种颜色的点中最靠右的位置打上 1 的标记,询问 \([l, r]\) 答案即为 \(query_r - query_{l - 1}\),可以证明,如果一个相同颜色的点的位置对答案有贡献,那么最靠右的位置也一定能作贡献。
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, m;
struct BIT
{
int tr[N];
void update(int x, int v)
{
for(; x <= N - 10; x += (-x) & x)
tr[x] += v;
}
int query(int x)
{
if(x == 0) return 0;
int sum = 0;
for(; x; x -= (-x) & x)
sum += tr[x];
return sum;
}
void clear() { memset(tr, 0, sizeof tr); }
} bit;
struct node
{
int l, r, id;
bool operator < (const node &W) const
{
return r < W.r;
}
} a[N];
int p[N];
int to[N];
int ans[N];
signed main()
{
n = read();
for(int i = 1; i <= n; i ++)
p[i] = read();
m = read();
for(int i = 1; i <= m; i ++)
a[i] = {read(), read(), i};
sort(a + 1, a + m + 1);
int rr = 1;
for(int i = 1; i <= m; i ++)
{
int id = a[i].id;
while(rr <= a[i].r)
{
if(to[p[rr]]) bit.update(to[p[rr]], -1);
bit.update(rr, 1);
to[p[rr]] = rr;
rr ++;
}
ans[id] = bit.query(a[i].r) - bit.query(a[i].l - 1);
}
for(int i = 1; i <= m; i ++)
cout << ans[i] << '\n';
return 0;
}
[HEOI2012]采花
与上一道题类似,只不过若颜色个数等于 1 也不计入贡献,那么我们只需要把贡献放到次靠右的位置上即可。
Code
#include <algorithm>
#include <cstring>
#include <iostream>
#define speedup (ios::sync_with_stdio(0), cin.tie(0), cout.tie(0))
#define int long long
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 2e6 + 10;
int tr[N];
void update(int a, int b)
{
if(a <= 0) return ;
for(; a <= N - 5; a += (-a) & a)
tr[a] += b;
}
int query(int a) {int sum = 0; for(; a ; a -= a & (-a)) sum += tr[a]; return sum;}
int n, k, m;
int a[N];
int p[N][3];
struct Q
{
int l, r, id;
bool operator <(const Q &W) const { return r < W.r; }
} q[N];
int ans[N];
signed main()
{
speedup;
cin >> n >> k >> m;
for(int i = 1; i <= n; i ++)
cin >> a[i];
for(int i = 1; i <= m; i ++)
cin >> q[i].l >> q[i].r, q[i].id = i;
sort(q + 1, q + m + 1);
int now = 0;
for(int i = 1; i <= m; i ++)
{
int r = q[i].r;
while(now <= r)
{
update(p[a[now]][1], -1);
update(p[a[now]][2], 1);
p[a[now]][1] = p[a[now]][2];
p[a[now]][2] = now;
now ++;
}
ans[q[i].id] = query(q[i].r) - query(q[i].l - 1);
}
for(int i = 1; i <= m; i ++)
cout << ans[i] << '\n';
return 0;
}
[POI2015] LOG
可以发现,如果一个数大于等于 \(s\),那么它肯定会被一直选中,设有 \(cnt\) 个数大于 \(s\)。
结论:若满足
\[(c - cnt)s \le\sum_{0 < p_i < s}p_i
\]
则这次询问有解,否则无解。
则需要维护:
- 所有小于 \(s\) 的数的和
- 所有大于等于 \(s\) 的数的数量
这都可以用树状数组做
Code
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define int long long
using namespace std;
const int N = 2e6 + 10;
int n, m;
struct BIT
{
int tr[N];
void update(int a, int b)
{
for(; a <= m + 3; a += (-a) & a)
tr[a] += b;
}
int query(int a)
{
int sum = 0;
for(; a; a -= (-a) & a)
sum += tr[a];
return sum;
}
} bit1, bit2;
struct Q
{
int op, a, b;
} q[N];
int disc[N], idx;
int p[N], pp[N];
signed main()
{
cin >> n >> m;
for(int i = 1; i <= m; i ++)
{
char ch;
int a, b;
cin >> ch >> a >> b;
q[i] = {ch == 'Z', a, b};
disc[++ idx] = b;
// 0 修改,1 查询
}
sort(disc + 1, disc + idx + 1);
idx = unique(disc + 1, disc + idx + 1) - disc - 1;
for(int i = 1; i <= m; i ++)
{
int op = q[i].op, a = q[i].a, b = lower_bound(disc + 1, disc + idx + 1, q[i].b) - disc;
if(op)
{
if(q[i].b * (a - (bit1.query(m + 1) - bit1.query(b - 1))) <= bit2.query(b - 1))
puts("TAK");
else
puts("NIE");
}
else
{
if(p[a])
bit1.update(pp[a], -1), bit2.update(pp[a], -p[a]);
p[a] = q[i].b, pp[a] = b;
bit1.update(b, 1);
bit2.update(b, p[a]);
}
}
return 0;
}
[JSOI2009] 计数问题
首先考虑一维问题如何解,只需要开 \(c\) 个树状数组,若一个位置上有该颜色就在对应的颜色树状数组上打上1。
二维的问题多加一维即可。
Code
#include <algorithm>
#include <cstring>
#include <iostream>
#define speedup (ios::sync_with_stdio(0), cin.tie(0), cout.tie(0))
#define int long long
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 2e6 + 10;
int tr[N];
void update(int a, int b)
{
if(a <= 0) return ;
for(; a <= N - 5; a += (-a) & a)
tr[a] += b;
}
int query(int a) {int sum = 0; for(; a ; a -= a & (-a)) sum += tr[a]; return sum;}
int n, k, m;
int a[N];
int p[N][3];
struct Q
{
int l, r, id;
bool operator <(const Q &W) const { return r < W.r; }
} q[N];
int ans[N];
signed main()
{
speedup;
cin >> n >> k >> m;
for(int i = 1; i <= n; i ++)
cin >> a[i];
for(int i = 1; i <= m; i ++)
cin >> q[i].l >> q[i].r, q[i].id = i;
sort(q + 1, q + m + 1);
int now = 0;
for(int i = 1; i <= m; i ++)
{
int r = q[i].r;
while(now <= r)
{
update(p[a[now]][1], -1);
update(p[a[now]][2], 1);
p[a[now]][1] = p[a[now]][2];
p[a[now]][2] = now;
now ++;
}
ans[q[i].id] = query(q[i].r) - query(q[i].l - 1);
}
for(int i = 1; i <= m; i ++)
cout << ans[i] << '\n';
return 0;
}
[POI2015] LOG
可以发现,如果一个数大于等于 \(s\),那么它肯定会被一直选中,设有 \(cnt\) 个数大于 \(s\)。
结论:若满足
\[(c - cnt)s \le\sum_{0 < p_i < s}p_i
\]
则这次询问有解,否则无解。
则需要维护:
- 所有小于 \(s\) 的数的和
- 所有大于等于 \(s\) 的数的数量
这都可以用树状数组做
Code
#include <algorithm>
#include <cstring>
#include <iostream>
#define speedup (ios::sync_with_stdio(0), cin.tie(0), cout.tie(0))
#define int long long
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 3e2 + 10, M = 110;
int n, m;
int a[N][N];
int tr[N][N][M];
void update(int x, int y, int c, int v)
{
for(int i = x; i <= n + 1; i += (-i) & i)
for(int j = y; j <= m + 1; j += (-j) & j)
tr[i][j][c] += v;
}
int query(int x, int y, int c)
{
int sum = 0;
for(int i = x; i; i -= i & (-i))
for(int j = y; j; j -= j & (-j))
sum += tr[i][j][c];
return sum;
}
int Query(int x1, int x2, int y1, int y2, int c)
{
return query(x2, y2, c) - query(x1 - 1, y2, c) - query(x2, y1 - 1, c) + query(x1 - 1, y1 - 1, c);
}
signed main()
{
speedup;
cin >> n >> m;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
cin >> a[i][j], update(i, j, a[i][j], 1);
int T;
cin >> T;
while(T --)
{
int op, x1, x2, y1, y2, c;
cin >> op;
if(op == 1)
{
cin >> x1 >> y1 >> c;
update(x1, y1, a[x1][y1], -1);
a[x1][y1] = c;
update(x1, y1, c, 1);
}
else
{
cin >> x1 >> x2 >> y1 >> y2 >> c;
cout << Query(x1, x2, y1, y2, c) << '\n';
}
}
return 0;
}
[NOIP2017 提高组] 列队
\(70\%\):
前 \(50\%\) 离散化询问就好了。
剩下的可以用 01 树状数组做,若一个数未被删除就打上 1 否则为 0,然后在树状数组上二分出要删除的位置,这样就可以在 \(O(\log^2 n)\) 内删除元素并插入至末尾。
\(100\%\) 咕咕咕