【LOJ3043】「ZJOI2019」线段树

【题目链接】

【思路要点】

  • 不难发现题目要求计算所有操作集合的子集对应的 t a g tag tag 数之和。
  • 分开考虑每个节点上 t a g tag tag 的存在情况,我们希望设计一个能够转移的状态。
  • 一种可行的方式是在状态中记录 0 / 1 0/1 0/1 二元组 ( s e l f , a n c e s t o r ) (self,ancestor) (self,ancestor) 分别表示节点本身及其任意祖先上是否存在标记。
  • 一次修改将对所有点的状态进行如下转移:
  • 令被区间定位到的点为 ( 1 ) (1) (1) 类点;被访问到但没有被定位到的点为 ( 2 ) (2) (2) 类点;父亲被访问到但本身没有被定位到的点为 ( 3 ) (3) (3) 类点; ( 1 ) (1) (1) 类点的子节点为 ( 0 ) (0) (0) 类点;剩余节点,也即 ( 3 ) (3) (3) 类点的子节点为 ( 4 ) (4) (4) 类点。
  • 对于 ( 1 ) (1) (1) 类点,有 s e l f ⇐ 1 , a n c e s t o r ⇐ 0 self\Leftarrow 1,ancestor\Leftarrow 0 self1,ancestor0
  • 对于 ( 2 ) (2) (2) 类点,有 s e l f ⇐ 0 , a n c e s t o r ⇐ 0 self\Leftarrow 0,ancestor\Leftarrow 0 self0,ancestor0
  • 对于 ( 3 ) (3) (3) 类点,有 s e l f ⇐ a n c e s t o r ∣ s e l f , a n c e s t o r ⇐ 0 self\Leftarrow ancestor|self,ancestor\Leftarrow 0 selfancestorself,ancestor0
  • 对于 ( 4 ) (4) (4) 类点,有 s e l f ⇐ s e l f , a n c e s t o r ⇐ a n c e s t o r self\Leftarrow self,ancestor\Leftarrow ancestor selfself,ancestorancestor
  • 对于 ( 0 ) (0) (0) 类点,有 s e l f ⇐ s e l f , a n c e s t o r ⇐ 1 self\Leftarrow self,ancestor\Leftarrow 1 selfself,ancestor1
  • 以上转移均可以写为矩阵的形式,因此直接用线段树维护 d p dp dp 向量,以矩阵作为标记即可。
  • 时间复杂度 O ( 4 3 × M L o g N ) O(4^3\times MLogN) O(43×MLogN)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 998244353;
const int Trans[5][4][4] = {
	{{1, 0, 1, 0}, {0, 1, 0, 1}, {0, 0, 2, 0}, {0, 0, 0, 2}},
	{{1, 1, 0, 0}, {0, 2, 0, 0}, {0, 1, 1, 0}, {0, 1, 0, 1}},
	{{2, 0, 0, 0}, {1, 1, 0, 0}, {1, 0, 1, 0}, {1, 0, 0, 1}},
	{{2, 0, 0, 0}, {0, 2, 0, 0}, {0, 1, 1, 0}, {0, 1, 0, 1}},
	{{2, 0, 0, 0}, {0, 2, 0, 0}, {0, 0, 2, 0}, {0, 0, 0, 2}}
};
// 0: self <- self, ancestor <- 1
// 1: self <- 1, ancestor <- 0
// 2: self <- 0, ancestor <- 0
// 3: self <- ancestor | self, ancestor <- 0
// 4: do nothing
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct vec {int a[4]; };
//0 : none, 1 : self, 2 : ancestor, 3 : both
struct mat {int a[4][4]; } trans[5];
vec operator + (const vec &a, const vec &b) {
	vec ans;
	for (int i = 0; i <= 3; i++)
		ans.a[i] = (a.a[i] + b.a[i] > P) ? (a.a[i] + b.a[i] - P) : (a.a[i] + b.a[i]);
	return ans;
}
vec operator * (const vec &a, const mat &b) {
	vec ans;
	for (int j = 0; j <= 3; j++) {
		ll res = 0;
		for (int i = 0; i <= 3; i++)
			res += 1ll * a.a[i] * b.a[i][j];
		ans.a[j] = res % P;
	}
	return ans;
}
mat operator * (const mat &a, const mat &b) {
	mat ans;
	for (int i = 0; i <= 3; i++)
	for (int k = 0; k <= 3; k++) {
		ll res = 0;
		for (int j = 0; j <= 3; j++)
			res += 1ll * a.a[i][j] * b.a[j][k];
		ans.a[i][k] = res % P;
	}
	return ans;
}
mat unit() {
	mat ans;
	for (int i = 0; i <= 3; i++)
	for (int j = 0; j <= 3; j++)
		ans.a[i][j] = i == j;
	return ans;
}
struct SegmentTree {
	struct Node {
		mat tag;
		bool unit;
		int lc, rc;
		vec val, sum;
	} a[MAXN * 2];
	int n, root, size;
	void update(int root) {
		a[root].sum = a[root].val;
		if (a[root].lc) a[root].sum = a[root].sum + a[a[root].lc].sum;
		if (a[root].rc) a[root].sum = a[root].sum + a[a[root].rc].sum;
	}
	void build(int &root, int l, int r) {
		root = ++size;
		a[root].tag = unit();
		a[root].unit = true;
		a[root].val.a[0] = 1;
		a[root].sum.a[0] = 1;
		if (l == r) return;
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
		update(root);
	}
	void init(int x) {
		n = x;
		root = size = 0;
		build(root, 1, n);
	}
	void pushdown(int root) {
		if (!a[root].unit && a[root].lc) {
			int tmp = a[root].lc;
			a[tmp].unit = false;
			a[tmp].val = a[tmp].val * a[root].tag;
			a[tmp].sum = a[tmp].sum * a[root].tag;
			a[tmp].tag = a[tmp].tag * a[root].tag;
			tmp = a[root].rc;
			a[tmp].unit = false;
			a[tmp].val = a[tmp].val * a[root].tag;
			a[tmp].sum = a[tmp].sum * a[root].tag;
			a[tmp].tag = a[tmp].tag * a[root].tag;
			a[root].unit = true;
			a[root].tag = unit();
		}
	}
	void puttag(int root, mat trans) {
		a[root].tag = a[root].tag * trans;
		a[root].sum = a[root].sum * trans;
		a[root].val = a[root].val * trans;
		a[root].unit = false;
	}
	void pushed(int root) {
		pushdown(root);
		a[root].val = a[root].val * trans[3];
		if (a[root].lc) puttag(a[root].lc, trans[4]);
		if (a[root].rc) puttag(a[root].rc, trans[4]);
		update(root);
	}
	void modify(int root, int l, int r, int ql, int qr) {
		pushdown(root);
		if (l == ql && r == qr) {
			a[root].val = a[root].val * trans[1];
			if (a[root].lc) puttag(a[root].lc, trans[0]);
			if (a[root].rc) puttag(a[root].rc, trans[0]);
			update(root);
			return;
		}
		a[root].val = a[root].val * trans[2];
		int mid = (l + r) / 2;
		if (mid >= qr) {
			pushed(a[root].rc);
			modify(a[root].lc, l, mid, ql, qr);
		} else if (mid + 1 <= ql) {
			pushed(a[root].lc);
			modify(a[root].rc, mid + 1, r, ql, qr);
		} else {
			modify(a[root].lc, l, mid, ql, mid);
			modify(a[root].rc, mid + 1, r, mid + 1, qr);
		}
		update(root);
	}
	void modify(int l, int r) {
		modify(root, 1, n, l, r);
	}
	int query() {
		return (a[root].sum.a[1] + a[root].sum.a[3]) % P;
	}
} ST;
int n, m;
int main() {
	read(n), read(m);
	for (int i = 0; i <= 4; i++) {
		for (int j = 0; j <= 3; j++)
		for (int k = 0; k <= 3; k++)
			trans[i].a[j][k] = Trans[i][j][k];
	}
	ST.init(n);
	for (int i = 1; i <= m; i++) {
		int opt, l, r; read(opt);
		if (opt == 1) {
			read(l), read(r);
			ST.modify(l, r);
		} else writeln(ST.query());
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值