LibreOJ #2980 [THUSCH 2017] 大魔法师 题解

更多文章可以在本人的个人小站:https://kaiserwilheim.github.io 查看。
转载请注明出处。


首先看题:

题目要求我们在 [ 1 , n ] [ 1,n ] [1,n] 的区间上维护三个值 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci,要求支持查询区间和和下面六中操作:

  1. 区间内 A i = A i + B i A_i = A_i + B_i Ai=Ai+Bi
  2. 区间内 B i = B i + C i B_i = B_i + C_i Bi=Bi+Ci
  3. 区间内 C i = C i + A i C_i = C_i + A_i Ci=Ci+Ai
  4. 区间内 A i = A i + k A_i = A_i + k Ai=Ai+k k k k 给定);
  5. 区间内 B i = B i × k B_i = B_i \times k Bi=Bi×k k k k 给定);
  6. 区间内 C i = k C_i = k Ci=k k k k 给定)。

显然这道题需要使用线段树来维护区间操作。
但是我们不能给每一个操作附上一个懒标记,最后懒标记下放的时候还不得麻烦死。

我们可以尝试一下转化一下我们的操作。

众所周知,一个矩阵乘以一个单位矩阵 I I I 还等于它本身。
单位矩阵的形状是这样的: [ 1 1 ⋱   1 ] \begin{bmatrix}1 & & &\\ & 1 & & \\ & & \ddots &\\\ & & & 1 \end{bmatrix} 1 11

如果我们将 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci 三个数值化作一个矩阵的话,那么这个矩阵 [ A i B i C i ] \begin{bmatrix} A_i & B_i & C_i \end{bmatrix} [AiBiCi] 所对应的单位矩阵是这个样子的: [ 1 1 1 ] \begin{bmatrix}1&&\\&1&\\&&1\end{bmatrix} 111

[ A i B i C i ] \begin{bmatrix} A_i & B_i & C_i \end{bmatrix} [AiBiCi] 想要变成 [ A i + B i B i C i ] \begin{bmatrix} A_i+B_i & B_i & C_i \end{bmatrix} [Ai+BiBiCi] 的话可以让它乘以一个 [ 1 1 1 1 ] \begin{bmatrix}1&&\\1&1&\\&&1\end{bmatrix} 1111 ( 2 , 1 ) (2,1) (2,1) 处多出来的那个1就代表着给结果行矩阵的第1个位置加上1个原先的行矩阵矩阵第2个位置的值,也就相当于是给结果的 A i A_i Ai 加上了一个 B i B_i Bi

同理,第二个操作乘以的是一个 [ 1 1 1 1 ] \begin{bmatrix}1&&\\&1&\\&1&1\end{bmatrix} 1111 ,第三个操作乘以的是一个 [ 1 1 1 1 ] \begin{bmatrix}1&&1\\&1&\\&&1\end{bmatrix} 1111

然后就是可恶的第四个操作。我们需要给 A i A_i Ai 加上一个 k k k,但是我们无法从刚才的房费里面推出来一个类似的方法。

于是我们可以考虑将我们维护的矩阵由 [ A i B i C i ] \begin{bmatrix} A_i & B_i & C_i \end{bmatrix} [AiBiCi] 变为 [ A i B i C i 1 ] \begin{bmatrix} A_i & B_i & C_i & 1 \end{bmatrix} [AiBiCi1]
这样我们就只需要乘以一个 [ 1 1 1 k 1 ] \begin{bmatrix}1&&&\\&1&&\\&&1&\\k&&&1\end{bmatrix} 1k111 即可。

为了适配我们新的需要维护的矩阵,前面三个就变成了 [ 1 1 1 1 1 ] \begin{bmatrix}1&&&\\1&1&&\\&&1&\\&&&1\end{bmatrix} 11111 [ 1 1 1 1 1 ] \begin{bmatrix}1&&&\\&1&&\\&1&1&\\&&&1\end{bmatrix} 11111 [ 1 1 1 1 1 ] \begin{bmatrix}1&&1&\\&1&&\\&&1&\\&&&1\end{bmatrix} 11111

第五个操作的思路也很简单,只需要乘以一个 [ 1 k 1 1 ] \begin{bmatrix}1&&&\\&k&&\\&&1&\\&&&1\end{bmatrix} 1k11 即可。

第六个操作有点难,我们可以先把 C i C_i Ci 变成0,通过乘以一个 [ 1 1 1 ] \begin{bmatrix}1&&&\\&1&&\\&&&\\&&&1\end{bmatrix} 111 ,然后再乘以一个 [ 1 1 1 k 1 ] \begin{bmatrix}1&&&\\&1&&\\&&1&\\&&k&1\end{bmatrix} 111k1 ,就可以给 C i C_i Ci 赋成 k k k 了。
最终效果跟直接乘以 [ 1 1 k 1 ] \begin{bmatrix}1&&&\\&1&&\\&&&\\&&k&1\end{bmatrix} 11k1 效果一样。

于是我们的线段树只需要维护一个区间乘和区间求和即可。

矩阵结构体:

struct Matrix
{
	int n, m;
	ll a[5][5];
	Matrix() {}
	Matrix(int _n, int _m) :n(_n), m(_m)
	{
		memset(a, 0, sizeof(a));
	}
	Matrix operator + (const Matrix &rhs)
	{
		Matrix res(n, m);
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= m; j++)
				res.a[i][j] = (a[i][j] + rhs.a[i][j]) % p;
		return res;
	}
	Matrix operator * (const Matrix &rhs)
	{
		Matrix res(n, rhs.m);
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= rhs.m; j++)
				for(int k = 1; k <= m; k++)
					res.a[i][j] = (res.a[i][j] + a[i][k] * rhs.a[k][j]) % p;
		return res;
	}
};

总代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 250010;
const ll p = 998244353;
struct Matrix
{
	int n, m;
	ll a[5][5];
	Matrix() {}
	Matrix(int _n, int _m) :n(_n), m(_m)
	{
		memset(a, 0, sizeof(a));
	}
	Matrix operator + (const Matrix &rhs)
	{
		Matrix res(n, m);
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= m; j++)
				res.a[i][j] = (a[i][j] + rhs.a[i][j]) % p;
		return res;
	}
	Matrix operator * (const Matrix &rhs)
	{
		Matrix res(n, rhs.m);
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= rhs.m; j++)
				for(int k = 1; k <= m; k++)
					res.a[i][j] = (res.a[i][j] + a[i][k] * rhs.a[k][j]) % p;
		return res;
	}
};
Matrix num[N];
struct SegTree
{
	int l, r;
	Matrix sum, tag;
}tr[N << 2];
Matrix base1, base2, base3, base4, base5, base6;
Matrix base;
void pushdown(int p)
{
	auto &root = tr[p], &left = tr[p << 1], &rght = tr[p << 1 | 1];
	left.sum = left.sum * root.tag;
	rght.sum = rght.sum * root.tag;
	left.tag = left.tag * root.tag;
	rght.tag = rght.tag * root.tag;
	root.tag = base;
	return;
}
void build(int p, int l, int r)
{
	tr[p].l = l, tr[p].r = r;
	if(l == r)
	{
		tr[p].sum = num[l];
		tr[p].tag = base;
		return;
	}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid);
	build(p << 1 | 1, mid + 1, r);
	tr[p].sum = tr[p << 1].sum + tr[p << 1 | 1].sum;
	tr[p].tag = base;
	return;
}

void segadd(int p, int l, int r, Matrix k)
{
	if(tr[p].l >= l && tr[p].r <= r)
	{
		tr[p].tag = tr[p].tag * k;
		tr[p].sum = tr[p].sum * k;
		return;
	}
	pushdown(p);
	int mid = (tr[p].l + tr[p].r) >> 1;
	if(l <= mid)segadd(p << 1, l, r, k);
	if(r > mid)segadd(p << 1 | 1, l, r, k);
	tr[p].sum = tr[p << 1].sum + tr[p << 1 | 1].sum;
	return;
}

Matrix segsum(int p, int l, int r)
{
	if(tr[p].l >= l && tr[p].r <= r)return tr[p].sum;
	pushdown(p);
	int mid = (tr[p].l + tr[p].r) >> 1;
	Matrix res(4, 4);
	if(l <= mid)res = res + segsum(p << 1, l, r);
	if(r > mid)res = res + segsum(p << 1 | 1, l, r);
	return res;
}

void init()
{
	base = Matrix(4, 4);
	for(int i = 1; i <= 4; i++)
		base.a[i][i] = 1;
	/*
	base1:| base2:| base3:
	1 0 0 | 1 0 0 | 1 0 1
	1 1 0 | 0 1 0 | 0 1 0
	0 0 1 | 0 1 1 | 0 0 1
	*/
	base1 = base;
	base1.a[2][1] = 1;
	base2 = base;
	base2.a[3][2] = 1;
	base3 = base;
	base3.a[1][3] = 1;
	/*
	base4:  | base5:  | base6:
	1 0 0 0 | 1 0 0 0 | 1 0 0 0
	0 1 0 0 | 0 v 0 0 | 0 1 0 0
	0 0 1 0 | 0 0 1 0 | 0 0 0 0
	v 0 0 1 | 0 0 0 1 | 0 0 v 1
	*/
	base4 = base;
	base5 = base;
	base6 = base;
	base6.a[3][3] = 0;
}
int main()
{
	init();
	int n;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
	{
		num[i] = Matrix(1, 4);
		scanf("%lld%lld%lld", &num[i].a[1][1], &num[i].a[1][2], &num[i].a[1][3]);
		num[i].a[1][4] = 1;
	}
	build(1, 1, n);
	int m;
	scanf("%d", &m);
	while(m--)
	{
		int op, l, r, v;
		scanf("%d%d%d", &op, &l, &r);
		if(op == 1)
		{
			segadd(1, l, r, base1);
		}
		else if(op == 2)
		{
			segadd(1, l, r, base2);
		}
		else if(op == 3)
		{
			segadd(1, l, r, base3);
		}
		else if(op == 4)
		{
			scanf("%d", &v);
			base4.a[4][1] = v;
			segadd(1, l, r, base4);
		}
		else if(op == 5)
		{
			scanf("%d", &v);
			base5.a[2][2] = v;
			segadd(1, l, r, base5);
		}
		else if(op == 6)
		{
			scanf("%d", &v);
			base6.a[4][3] = v;
			segadd(1, l, r, base6);
		}
		else if(op == 7)
		{
			Matrix res = segsum(1, l, r);
			printf("%lld %lld %lld\n", res.a[1][1], res.a[1][2], res.a[1][3]);
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值