[清华集训 2014] 玄学

Update \text{Update} Update:我之前讲的是个什么鬼… 如果想看看人话版本 戳这


感觉自己被坑骗了。。。题目明明写了所有数据不超过int,敢情是输入数据不超int???迷惑行为

题目感觉有点绕,我尽量 不口胡。

首先我们搞一颗线段树1,树表示插入序列的编号。(如,在Q行中第i个出现插入操作)

其实拿到这道题我的本能是树套树的。(其实是打树套树打疯了神志不清 )那么为什么我们不用树套树而可以用简洁明快的不知什么鬼算法呢?我们先想想树套树:线段树1中的每个节点套一个长度为n的区间,此时的树套树其实是可以承受每个点都有不一样的lazy,value……但不知什么鬼算法不一样,是这样搞的:我们将长度为n的区间换一下,换成几个小区间,每个区间不止一个点,而是一段点有相同的lazy标记。这样可以保证时空复杂度的可行。

清楚定义后一切就变得 豁, 然, 开, 朗 起来。

思考如何更新。首先,我们知道后面的操作会覆盖前面的,所以就确定了我们的更新顺序:先算左儿子再算右儿子。假设出现了这样的情况:

|------------|------------------|—| l s o n lson lson
|-------|------------------------|–| r s o n rson rson
         a    b                    cd e

根据之前的定义,我们要将 a , b , c , d , e a,b,c,d,e a,b,c,d,e作为分界线划分n这个区间作为新的区间。那么 l 1 l1 l1 l 2 l2 l2指针的移动规则不言而喻。

而且我们知道要在 r = = c n t r==cnt r==cnt时更新,因为这样子区间才插入完了,才可以合并。

最后查询时二分找出pos在被分成的哪段区间里就行了。

啊我终于讲完了谢谢大家祝大家春节愉快阖家幸福有钱出钱没钱赏赞。

#include<cstdio>
#include<iostream>
#define int long long
using namespace std;

const int N = 6e5 + 2, M = N * 30;
int ans, aa, bb, A, B, ql, qr, pos, type, n, m, v[N], Q, cnt, tot, L[N << 2], R[N << 2], ll[M], rr[M], a[M], b[M];

int read() {
	int x = 0, f = 1;
	char s = getchar();
	while(s > '9' || s < '0') {
		if(s == '-') f = -1;
		s = getchar();
	}
	while(s >= '0' && s <= '9') {
		x = (x << 1) + (x << 3) + (s ^ 48);
		s = getchar();
	}
	return x * f;
} 

void pushUp(const int o) {
	int l1 = L[o << 1], l2 = L[o << 1 | 1], r1 = R[o << 1], r2 = R[o << 1 | 1], l0 = 1;
	L[o] = tot + 1;
	while(l1 <= r1 || l2 <= r2) {
		ll[++ tot] = l0; rr[tot] = min(rr[l1], rr[l2]);
		a[tot] = a[l1] * a[l2] % m;
		b[tot] = (a[l2] * b[l1] + b[l2]) % m;
		l0 = rr[tot] + 1;
		if(rr[l1] < rr[l2]) ++ l1;
		else if(rr[l1] > rr[l2]) ++ l2;
		else ++ l1, ++ l2;
	}
	R[o] = tot;
}

void change(const int o, const int l, const int r) {
	if(l > cnt || r < cnt) return;
	if(l == r) {
		L[o] = tot + 1;
		if(ql > 1) ll[++ tot] = 1, rr[tot] = ql - 1, a[tot] = 1;
		ll[++ tot] = ql, rr[tot] = qr, a[tot] = A, b[tot] = B;
		if(qr < n) ll[++ tot] = qr + 1, rr[tot] = n, a[tot] = 1;
		R[o] = tot;
		return;
	}
	int mid = l + r >> 1;
	change(o << 1, l, mid);
	change(o << 1 | 1, mid + 1, r);
	if(r == cnt) pushUp(o);
}

int find(int l, int r) {
	int res = 0;
	while(l <= r) {
		int mid = l + r >> 1;
		if(rr[mid] < pos) l = mid + 1;
		else r = mid - 1, res = mid;
	}
	return res;
}

void ask(const int o, const int l, const int r) {
	if(ql > r || qr < l) return;
	if(l >= ql && r <= qr) {
		int id = find(L[o], R[o]);
		aa = aa * a[id] % m;
		bb = (a[id] * bb + b[id]) % m;
		return;
	}
	int mid = l + r >> 1;
	ask(o << 1, l, mid);
	ask(o << 1 | 1, mid + 1, r);
}

signed main() {
	int op, a, b;
	type = (read() & 1), n = read(), m = read();
	for(int i = 1; i <= n; ++ i) v[i] = read();
	Q = read();
	for(int i = 1; i <= Q; ++ i) {
		op = read(), ql = read(), qr = read();
		if(type) ql ^= ans, qr ^= ans;
		if(op == 1) {
			A = read() % m, B = read() % m;
			++ cnt;
			change(1, 1, Q);
		}
		else {
			pos = read();
			if(type) pos ^= ans;
			aa = 1, bb = 0;
			ask(1, 1, Q);
			ans = (aa * v[pos] + bb) % m;
			printf("%lld\n", ans);
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值