树状数组 好题整理
树状数组 好题整理
[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\%\) 咕咕咕

 应该会更新(吧
        应该会更新(吧
     
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号