树状数组 好题整理

树状数组 好题整理

[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\%\) 咕咕咕

posted @ 2023-04-29 19:08  MoyouSayuki  阅读(27)  评论(0编辑  收藏  举报
:name :name