树状数组好题
树状数组的题目一般有两种。哪两种?我们可以看看树状数组的定义。
我们树状数组有个数组 \(f\)。那么这个 \(f\) 有哪几种情况呢?
如果 \(f\) 的下标存的是原序列的下标,那么树状数组就是求原序列区间和/min/max/.....
如果 \(f\) 的下标存的是原序列的值,那么树状数组就是用来二位数点的。
废话不多说,看题。
P3605 [USACO17JAN] Promotion Counting P
首先 dfs 在所难免。
考虑怎么统计答案。
- 考虑
dfs后离线统计。
我们考虑用dfn序来做。想要统计一个子树比自己权值大的数的个数,其实就是子树对应区间的比自己大的个数。简单来说,如果查询以 \(x\) 为根的子树比自己权值大的个数,我们就可以转换为序列上求 \([dfn[x],dfn[x]+sz[x]-1]\) 区间内比自己大的个数。主席树或线段树处理即可。 - 考虑
dfs在线统计
我们维护出 \(n\) 个树状数组,具体就是把以 \(x\) 为根的子树内的点的权值扔进树状数组BIT[x],让 \(f\) 的下标存储点的值,然后二位数点就行了。
但是我们的空间不能接受开 \(\mathcal O(n)\) 级别的树状数组,而且合并树状数组会多一个 \(log\)。怎么办呢?
实际上我们只需要开一个树状数组。毕竟能在若干个树状数组上数点,为什么不直接在一个树状数组上数点呢?
我们遍历完 \(x\) 的子树后去统计树状数组内大于自己权值的点的个数,最后也把 \(x\) 的权值扔进去。由于可能树状数组内部包含其他子树的信息,我们在遍历 \(x\) 之前先让 \(ans[x]\) 减去其他子树大于自己节点权值的个数即可。
两种方法均可,但是显然第二种好写,复杂度都是 \(\mathcal O(n\log n)\)。
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1e5+5;
struct BIT
{
int T[N];
void upd(int x)
{
for(;x;x-=x&-x)
T[x]++;
}
int query(int x)
{
int res=0;
for(;x<=N-5;x+=x&-x)
res+=T[x];
return res;
}
}T;
vector<int> G[N];
int n,a[N],ans[N],b[N];
void dfs(int x,int fa)
{
ans[x]-=T.query(a[x]);
for(int v:G[x])
{
if(v==fa) continue;
dfs(v,x);
}
ans[x]+=T.query(a[x]);
T.upd(a[x]);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],b[i]=a[i];
sort(b+1,b+1+n);
int m=unique(b+1,b+1+n)-b;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+1+m,a[i])-b;
for(int i=2;i<=n;i++)
{
int x;cin>>x;
G[x].pb(i),G[i].pb(x);
}
dfs(1,0);
for(int i=1;i<=n;i++)
cout<<ans[i]<<"\n";
return 0;
}
P1168 中位数
一眼对顶堆,但这不是我们今天的主角。
我们考虑能不能用树状数组做。首先考虑离散化,然后我们维护出一个树状数组。
这个时候就是求目前一个序列的第 \(k\) 大值。其中 \(k=\frac{i+1}{2}\)。
然后在树状数组上二分就行了。
代码不放了,感觉很好写吧。
P3760 [TJOI2017] 异或和
这题应该没有紫。至少我能自己做出来的应该都不算紫。
首先考虑二进制拆分。然后就是统计每一位上是否最后异或能出 \(1\),也就是说看在这一位上,产生 \(1\) 的贡献的个数是不是奇数。那么怎么求个数呢?
我们首先维护一个前缀和数组,这样区间和就变为两个点的减法,处理起来会简单一些。然后一次枚举每一进制位。
首先开两个树状数组是显然的,一个存储的是这一位是 \(0\) 的信息,另一个存 \(1\)。
然后我们考虑借位情况。我们假设对于对于某一位 \(i\),由于该位的对称性,我们这里讨论第 \(x\) 个数在 \(i\) 位上是 \(1\) 的情况,那么能与他产生 \(1\) 的贡献的有两种情况。
- 第 \(i\) 位是 \(0\) 且不借位。
- 第 \(i\) 位是 \(1\) 且借位。
如果我们把前 \(n\) 个数 \(i-1\) 位的值看作数组 \(b\),那么就是: - 第 \(i\) 位是 \(0\) 且小于等于 \(b_x\) 的 \(b\) 的个数。
- 第 \(i\) 位是 \(1\) 且大于 \(b_x\) 的 \(b\) 的个数。
这就是二位数点了,因此我们对于每一进制位做一次二位数点即可。复杂度 \(\mathcal O(n\log ^2V)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,V=1e6+5;
struct BIT
{
int t[V][2];
void clear(){memset(t,0,sizeof(t));}
void upd(int x)
{
int y=x;
for(;x<=V-5;x+=x&-x)
t[x][0]++;
for(;y;y-=y&-y)
t[y][1]++;
}
int query1(int x)
{
int res=0;
for(;x;x-=x&-x) res+=t[x][0];
return res;
}
int query2(int x)
{
int res=0;
for(;x<=V-5;x+=x&-x)
res+=t[x][1];
return res;
}
}T0,T1;
int n,a[N],b[N],cnt,ans,cnt0,cnt1;
int main()
{
cin>>n;
for(int i=1,x;i<=n;i++)
cin>>x,a[i]=a[i-1]+x;
cnt0=1;
for(int i=1;i<=n;i++)
{
int x=a[i]&1;
if(x) cnt+=cnt0,cnt1++;
else cnt+=cnt1,cnt0++;
}
if(cnt&1) ans=1;
for(int i=1;i<=20;i++)
{
T0.clear(),T1.clear(),T0.upd(1),cnt=0;
for(int j=1;j<=n;j++)
b[j]|=((a[j]>>(i-1))&1)<<(i-1);
for(int j=1;j<=n;j++)
{
int x=(a[j]>>i)&1;
if(x) cnt+=T0.query1(b[j]+1)+T1.query2(b[j]+2),T1.upd(b[j]+1);
else cnt+=T1.query1(b[j]+1)+T0.query2(b[j]+2),T0.upd(b[j]+1);
}
if(cnt&1) ans|=1<<i;
}
cout<<ans;
return 0;
}
P3369 【模板】普通平衡树
???????????????????????????????????????????????????????????????
是的,这道题可以树状数组做!
考虑把这道题除了 \(4\) 操作外的值全部装入一个数组,然后离线离散化。
对值开树状数组,然后遇到加入和删除分别进行 \(upd(a[i],1)\) 和 \(upd(a[i],-1)\) 即可。
后三个询问呢?\(4\) 直接 \(query(i)\),\(5,6\) 直接二分。
玄学树状数组,而且比块链复杂度优秀,比平衡树常数小。
#include<bits/stdc++.h>
using namespace std;
const int maxn=100050;
inline int read()
{
int x=0,t=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*t;
}
int n,q[maxn],a[maxn],p[maxn],tot=0,c[maxn];
int hash(int x){return lower_bound(q+1,q+1+tot,x)-q;}
int lowbit(int x){return x&-x;}
void add(int x,int p)
{
while(x<=tot)
{
c[x]+=p;
x+=lowbit(x);
}
}
int sum(int x)
{
int res=0;
while(x)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
int query(int x)
{
int t=0;
for(int i=19;i>=0;i--)
{
t+=1<<i;
if(t>tot||c[t]>=x) t-=1<<i;
else x-=c[t];
}
return q[t+1];
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
p[i]=read(),a[i]=read();
if(p[i]!=4) q[++tot]=a[i];
}
//hash
sort(q+1,q+1+tot);
tot=unique(q+1,q+1+tot)-(1+q);
for(int i=1;i<=n;i++)
{
if(p[i]==1) add(hash(a[i]),1);
if(p[i]==2) add(hash(a[i]),-1);
if(p[i]==3) printf("%d\n",sum(hash(a[i])-1)+1);
if(p[i]==4) printf("%d\n",query(a[i]));
if(p[i]==5) printf("%d\n",query(sum(hash(a[i])-1)));
if(p[i]==6) printf("%d\n",query(sum(hash(a[i]))+1));
}
return 0;
}
P2717 寒假作业
很多人都把这道题作为一道 cdq 入门题。
那么这道题树状数组怎么做呢?我们考虑把每个数都减去 \(k\)。
那么就是问有多少个子区间,平均值大于等于 \(0\)。换句话说,就是区间和大于等于 \(0\)。
我们考虑维护出前缀和数组,然后就是问 \((i,j)\) 的数量,使得 \(i\le j,sum_j\ge sum_{i-1}\)。然后就是经典二位数点了。
二位数点写的有点多了不想写了(。
P1972 [SDOI2009] HH的项链
首先我要说一句,莫队可做!!!!
这道题之前说了在线做法碾爆树状数组和莫队的主席树,但是我们还是要了解一下好写的树状数组做法。
这道题比较难的地方就是怎么让它区间颜色只产生 \(1\) 的贡献。我们考虑扫描线做法,然后每一次维护颜色的最靠右的贡献即可。
假设我们这个时候遇到了一个之前没遇到的颜色,我们就在这里打上 \(1\) 的标记;如果遇到一个之前遇到过的颜色,那么就撤销之前打过的标记,然后在这里打上 \(1\) 的标记。像这种打撤销标记的做法是扫描线经典做法了,省选也很喜欢出。
然后就做完了。查询的时候就是 \(query(r)-query(l-1)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e6 + 5;
ll n, a[N], nxt[N];
struct ANS
{
ll id, l;
ll r, ans;
} A[N];
bool CMP(ANS x, ANS y)
{
return x.r < y.r;
}
bool CMP2(ANS x, ANS y)
{
return x.id < y.id;
}
namespace BIT
{
ll T[N];
ll lowbit(ll x) {return x & -x;}
void add(ll x, ll k)
{
while (x <= n)
{
T[x] += k;
x += lowbit(x);
}
}
ll query(ll x)
{
ll res = 0;
while (x)
{
res += T[x];
x -= lowbit(x);
}
return res;
}
}
int main()
{
scanf("%lld", &n);
for (ll i = 1; i <= n; i++) scanf("%lld", a + i);
ll q;
scanf("%lld", &q);
for (ll i = 1; i <= q; i++)
{
scanf("%lld%lld", &A[i].l, &A[i].r);
A[i].id = i;
}
sort(A + 1, A + 1 + q, CMP);
ll pos = 1;
for (ll i = 1; i <= q; i++)
{
ll l = A[i].l, r = A[i].r;
while (pos <= r)
{
if (nxt[a[pos]]) BIT::add(nxt[a[pos]], -1);
BIT::add(pos, 1);
nxt[a[pos]] = pos;
pos++;
}
A[i].ans = BIT::query(r) - BIT::query(l - 1);
}
sort(A + 1, A + 1 + q, CMP2);
for (ll i = 1; i <= q; i++) cout << A[i].ans << endl;
return 0;
}
补充一下。如果这个时候给序列加上一个属性值 \(x_i\),每一次问区间内每一种颜色的最大属性值之和,怎么做呢?我们还是打撤销贡献,不同的是,我们还需要记录一个属性值的贡献即可。可以补充一下代码:
#include <bits/stdc++.h>//deepseek写的,老子懒得写了
using namespace std;
typedef long long ll;
const int MAXN = 5e5 + 5;
struct Query {
int l, r, idx;
};
int n, q;
int c[MAXN], x[MAXN];
Query queries[MAXN];
ll ans[MAXN];
vector<int> by_r[MAXN];
map<int, int> last_pos;
map<int, int> color_max;
// Fenwick Tree for prefix sums
struct FenwickTree {
vector<ll> bit;
int n;
FenwickTree(int n) : n(n), bit(n + 1, 0) {}
void update(int idx, ll delta) {
for (; idx <= n; idx += idx & -idx)
bit[idx] += delta;
}
ll query(int idx) {
ll res = 0;
for (; idx > 0; idx -= idx & -idx)
res += bit[idx];
return res;
}
ll query_range(int l, int r) {
return query(r) - query(l - 1);
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> c[i] >> x[i];
}
for (int i = 0; i < q; ++i) {
cin >> queries[i].l >> queries[i].r;
queries[i].idx = i;
by_r[queries[i].r].push_back(i);
}
FenwickTree ft(n);
for (int r = 1; r <= n; ++r) {
int color = c[r];
int value = x[r];
// Update the current max for the color
if (color_max.find(color) != color_max.end()) {
if (value > color_max[color]) {
// Subtract the old max
ft.update(last_pos[color], -color_max[color]);
// Add the new max
ft.update(r, value);
color_max[color] = value;
last_pos[color] = r;
}
} else {
color_max[color] = value;
last_pos[color] = r;
ft.update(r, value);
}
// Answer all queries ending at r
for (int idx : by_r[r]) {
int l = queries[idx].l;
ans[queries[idx].idx] = ft.query_range(l, r);
}
}
for (int i = 0; i < q; ++i) {
cout << ans[i] << '\n';
}
return 0;
}
当然这道补充题也可以莫队做,这里推荐莫队做了,因为如果不卡常的话莫队会更好写一些。
P6225 [eJOI 2019] 异或橙子
线段树唐题。那么现在不让用线段树否则斩立决,该怎么办呢?
那肯定是跟 \(l,u\) 的奇偶性有关呗。
如果 \(l,u\) 的奇偶性相同,\(l,u\) 中所有元素答案皆为 \(0\)。
如果奇偶性不同,那么答案就是 \(a_l\oplus a_{l+2}\oplus \cdots\oplus a_u\)。
开两个树状数组维护奇偶下标即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 2e5 + 5;
ll tree_0[N], tree_1[N];
ll n;
ll a[N];
ll lowbit(ll x) {
return (x & -x);
}
ll query(ll x) {
ll cnt = 0;
for (ll i = x; i; i -= lowbit(i)) {
cnt ^= tree_0[i];
}
return cnt;
}
void update(ll x, ll k) {
for (ll i = x; i <= n; i += lowbit(i)) {
tree_0[i] ^= k;
}
}
ll queryy(ll x) {
ll cnt = 0;
for(ll i = x; i; i -= lowbit(i)) {
cnt ^= tree_1[i];
}
return cnt;
}
void updatee(ll x, ll k) {
for (ll i = x; i <= n; i += lowbit(i)) {
tree_1[i] ^= k;
}
}
int main() {
ll m;
scanf("%lld%lld", &n, &m);
for (ll i = 1; i <= n; ++i) {
scanf("%lld", a + i);
if ((i & 1) == 0) {
update(i, a[i]);
} else {
updatee(i, a[i]);
}
}
while (m--) {
ll op;
scanf("%lld", &op);
if (op == 1) {
ll x, value;
scanf("%lld%lld", &x, &value);
ll k = x & 1;
if (k == 0) {
update(x, a[x] ^ value);
} else {
updatee(x, a[x] ^ value);
}
a[x] = value;
} else {
ll left, right;
scanf("%lld%lld", &left, &right);
if ((left + right) & 1) {
cout << 0 << endl;
continue;
}
ll k = left & 1;
if (k == 0) {
printf("%lld\n", query(right) ^ query(left - 1));
continue;
}
printf("%lld\n", queryy(right) ^ queryy(left - 1));
}
}
return 0;
}

浙公网安备 33010602011771号