[CF498D] Traffic Jams in the Land 题解

看题目似乎没什么思路,但是翻到数据范围,我们发现拥堵程度 \(a_i\) 和修改值的值域是 \([2,6]\),而边权为二时的条件是当前时间 \(t\) 满足 \(t\equiv 0 \pmod{a_i}\),也就是说点 \(i\) 对最终答案的贡献在 \(t\) 属于一定区间时内是相同的,而我们也可以把这个结论扩展到区间上去。

根据以上结论,我们只需要对齐区间内每个值的循环节,就可以保证即使进入区间的 \(t\) 是不同,我们依然可以把 \(t\) 对应到区间循环节的某个部位直接求出区间的贡献,再由数据范围可知,我们最长循环节长度为 60。因为需要支持单点修改,区间查询,并且答案都是从左到右计算,我们考虑使用线段树维护,时间复杂度 \(O(n\log_2n)\),因为要维护循环节的值,会带一个 60 的常数。

Code:

#include <bits/stdc++.h>
#include <bits/extc++.h>
#define ll long long
#define ull unsigned long long
#define m_p make_pair
#define m_t make_tuple
#define N 100010
using namespace std;
using namespace __gnu_pbds;
int val[N], tr[N << 2][60];
void build(int k, int l, int r)
{
	if (l == r)
	{
		for (int i = 0; i < 60; i++)
			tr[k][i] = 1 + !(i % val[l]);
		return;
	}
	int mid = l + r >> 1;
	build(k << 1, l, mid);
	build(k << 1 | 1, mid + 1, r);
	for (int i = 0; i < 60; i++)
		tr[k][i] = tr[k << 1][i] + tr[k << 1 | 1][(tr[k << 1][i] + i) % 60];
}
void tr_c(int k, int l, int r, int x)
{
	if (l > x || r < x)
		return;
	if (l == r && l == x)
	{
		for (int i = 0; i < 60; i++)
			tr[k][i] = 1 + !(i % val[l]);
		return;
	}
	int mid = l + r >> 1;
	if (x <= mid)
		tr_c(k << 1, l, mid, x);
	else
		tr_c(k << 1 | 1, mid + 1, r, x);
	for (int i = 0; i < 60; i++)
		tr[k][i] = tr[k << 1][i] + tr[k << 1 | 1][(tr[k << 1][i] + i) % 60];
}
int aans;
void tr_a(int k, int l, int r, int x, int y)
{
	if (l > y || r < x)
		return;
	if (l >= x && r <= y)
	{
		aans += tr[k][aans % 60];
		return;
	}
	int mid = l + r >> 1;
	if (x <= mid)
		tr_a(k << 1, l, mid, x, y);
	if (y > mid)
		tr_a(k << 1 | 1, mid + 1, r, x, y);
	return;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int n, q, x, y, ans;
	char opt;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> val[i];
	cin >> q;
	build(1, 1, n);
	while (q--)
	{
		cin >> opt >> x >> y;
		if (opt == 'C')
		{
			val[x] = y;
			tr_c(1, 1, n, x);
		}
		else
		{
			aans = 0;
			tr_a(1, 1, n, x, y - 1);
			cout << aans << "\n";
		}
	}

	return 0;
}

关于 tr_a 即询问函数,因为我们知道时间是从左到右计算的,线段树也是从左到右扫的,所以我这里干脆使用了全局变量,这样就可以比较方便的知道某个区间应该是算循环节的哪个部分。

题外话,%你赛考了这题,当时写的是分块,因为脑抽忘记线段树也是从左到右扫的,所以没写线段树,最后十五分钟突然想起来线段树好像也能维护,然后从左到右的查询不知道哪里写锅了,结果没写出来,暴扣 70。

posted @ 2024-02-21 16:16  -wryyy-  阅读(7)  评论(0编辑  收藏  举报