P9990 [Ynoi Easy Round 2023] TEST_90 题解

很容易想到离线扫描线。

考虑离线从 11nn 扫描 rr,同时开一棵线段树,线段树每个位置都是 0011,第 ii 个位置表示 [i,r][i,r] 中出现过的数的个数奇偶性。对于 i>ri>r,默认这个点为 00。则每个询问都是扫描到对应的 rr 时刻,区间 [l,r][l,r] 的历史版本和。

考虑扫描过程的转移。从前往后扫描时显然可以动态维护 ara_r 的上一次出现位置 pospos[1,pos][1,pos] 内的点,奇偶性不变,后面的点,奇变偶,偶变奇。

于是我们要维护支持区间反转,历史版本和的线段树。每个节点额外开个 hsum,tg0,tg1hsum,tg_0,tg_1,分别表示当前区间历史版本和,当前区间在目前操作序列中没有被反转几次,被反转几次。

具体看代码注释:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
using namespace std;

const int N = 1e6 + 5;

int n, m, a[N];

long long ans[N];
vector<pair<int, int>> v[N];

class SegmentTree
{
public:
	struct Node
	{
		int l, r;
		long long sum;
		long long hsum;
		bool tag;
		long long tg[2];
	}tr[N << 2];
	void pushup(int u)
	{
		tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
		tr[u].hsum = tr[u << 1].hsum + tr[u << 1 | 1].hsum;
	}
	void pushdown(int u)
	{
		tr[u << 1].hsum += tr[u << 1].sum * tr[u].tg[0] + tr[u].tg[1] * (tr[u << 1].r - tr[u << 1].l + 1 - tr[u << 1].sum); // 有 tr[u].tg[0] 个没有反转和 tr[u].tg[1] 个有反转
		if (tr[u].tag)
		{
			tr[u << 1].sum = (tr[u << 1].r - tr[u << 1].l + 1) - tr[u << 1].sum; // 正常维护 tag
		}
		tr[u << 1].tg[0] += tr[u].tg[tr[u << 1].tag], tr[u << 1].tg[1] += tr[u].tg[tr[u << 1].tag ^ 1];
		// 如果左儿子处于反转状态,即 tag = 1,那么 tg[0] 应该加上 u 点的 tg[1],tg[1] 同理
		tr[u << 1].tag ^= tr[u].tag;
		// 右儿子同理
		tr[u << 1 | 1].hsum += tr[u << 1 | 1].sum * tr[u].tg[0] + tr[u].tg[1] * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1 - tr[u << 1 | 1].sum);
		if (tr[u].tag)
		{
			tr[u << 1 | 1].sum = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) - tr[u << 1 | 1].sum;
		}
		tr[u << 1 | 1].tg[0] += tr[u].tg[tr[u << 1 | 1].tag], tr[u << 1 | 1].tg[1] += tr[u].tg[tr[u << 1 | 1].tag ^ 1];
		tr[u << 1 | 1].tag ^= tr[u].tag;
		tr[u].tag = 0;
		tr[u].tg[0] = tr[u].tg[1] = 0;
	}
	void build(int u, int l, int r)
	{
		tr[u] = { l, r, 0LL, 0LL, 0, {0LL, 0LL} };
		if (l == r) return;
		int mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
	}
	void update(int u, int l, int r)
	{
		if (tr[u].l >= l and tr[u].r <= r)
		{
			tr[u].tag ^= 1;
			tr[u].tg[tr[u].tag]++;
			tr[u].sum = (tr[u].r - tr[u].l + 1) - tr[u].sum;
			tr[u].hsum += tr[u].sum;
			return;
		}
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) update(u << 1, l, r);
		else
		{
			// 如果另一边不需要反转,也需要记录 hsum 和 tg
			tr[u << 1].tg[tr[u << 1].tag]++;
			tr[u << 1].hsum += tr[u << 1].sum;
		}
		if (r > mid) update(u << 1 | 1, l, r);
		else
		{
			// 如果另一边不需要反转,也需要记录 hsum 和 tg
			tr[u << 1 | 1].tg[tr[u << 1 | 1].tag]++;
			tr[u << 1 | 1].hsum += tr[u << 1 | 1].sum;
		}
		pushup(u);
	}
	long long query(int u, int l, int r)
	{
		if (tr[u].l >= l and tr[u].r <= r)
		{
			return tr[u].hsum;
		}
		pushdown(u);
		long long res = 0LL;
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) res = query(u << 1, l, r);
		if (r > mid) res = res + query(u << 1 | 1, l, r);
		return res;
	}
}sgt;

int lstpos[N];

int main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	cin >> m;
	for (int i = 1; i <= m; i++)
	{
		int l, r;
		cin >> l >> r;
		v[r].emplace_back(make_pair(i, l));
	}
	sgt.build(1, 1, n);
	for (int i = 1; i <= n; i++)
	{
		int p = lstpos[a[i]];
		lstpos[a[i]] = i;
		sgt.update(1, p + 1, i);
		for (auto& [id, l] : v[i])
		{
			ans[id] += sgt.query(1, l, i);
		}
	}
	for (int i = 1; i <= m; i++) cout << ans[i] << "\n";
	return 0;
}
posted @   HappyBobb  阅读(14)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示