ABC246Ex 01? Queries Solution更好的阅读体验戳此进入浅谈 DDP 与 广义矩阵乘法(戳此进入)引入例题 #1广义矩阵乘法DDP例题 #0例题 #0.5例题 #1例题 #2例题 #3UPD
这题的题解是从我写的 Blog 中截出来的,可以直接去看原 Blog。
题目 SP1043 GSS1 - Can you answer these queries I。
题面:对于序列
int
范围内。
有显然的 DP,令
一般的区间最大子段和转移为
但是每次询问都重新维护一遍显然会寄,所以考虑一些人类智慧。
首先对于普通的矩阵乘法,我们随便举个例子:
我们考虑把这玩意省略的东西写全一点:
我们发现这里一共有两种符号,
这就是广义矩阵乘法了。
然后为了让这东西能有点用,所以现在我们还需要让广义矩阵乘法具有结合律,也就是:
或者写成:
展开一下有:
不难发现,如果
所以我们便可得出这个结论——如果
为了便于理解,我们先引入一个更简单的例子。
题面:求斐波那契数列。
十分显然的
我们考虑一些让复杂度更高奇妙的方法。
我们考虑把这个转移矩阵记作
此时的
也就是说:
于是我们不如继续尝试把
这东西虽然可以转移的,但是自己观察一下,这东西的形式和最开始的柿子好像形式很相似,于是我们考虑如果不去选择
然后这样也很简单就能求出来
然后我们继续往下转移:
此时我们便能发现一些奇妙的推导:
于是这个时候我们便可以矩阵快速幂(因此我们才需要使广义矩阵之间满足结合律,否则用不了矩阵快速幂或者线段树猫树之类的)把原来的线性求,变成现在的
当然这只是最最基础的 DDP。
题目:LG-P1115 最大子段和。
最大字段和模板题,因为比较水所以这里就不叙述的太详细了,令
同时注意这里的矩阵是广义矩阵乘法,
然后像之前一样拆下去就可以得到一大串矩阵相乘,成功大幅增加常数成功提高了转移的扩展性。
现在让我们回到文章伊始提到的例题,这里我们再回顾一下:
题目 SP1043 GSS1 - Can you answer these queries I。
题面:对于序列
int
范围内。
有显然的 DP,令
一般的区间最大子段和转移为
和上一题一样,我们还是考虑令:
然后继续往下拆:
一直拆到边界,就是:
因为我们要的答案为
可以发现我们想要得到
为了便于书写,我们记:
则:
而在实际使用中我们还有很多小技巧与转化来让我们实现起来更简单:
如对于矩阵我们一般通过新开一个结构体然后重载 *
实现,这个时候我们可以考虑把初始矩阵补齐成
举个例子:
然后在重载矩阵的乘法的时候,我们应该注意,是否有可能会超出 int
或 long long
等,所以在我们取
然后如果严格按照上面的算法实现出来后,会发现一个问题,在某些特殊的时候答案会错误,经过 debug 我们发现当
我们考虑能否将初始矩阵
不难算出:
所以我们的式子就最终变成了:
现在这个式子就很好维护了。
这个时候我们观察那一大串
此时我们可能会想到树状数组,但是仔细思考一下就会发现是不行的。众所周知树状数组在运算上有一个类似差分的过程,也就是说需要有逆元的存在,但是在我们定义的矩阵和矩阵之间的广义矩乘构成的群中是否存在逆元呢?显然我们都知道矩阵求逆的一个必要条件是行列式不为
一个比较显然的东西就是线段树,每个节点存的是一个矩阵,或者说对应区间的矩阵乘起来(看到这里大概就又能意识到让矩阵满足结合律的重要性了)。当然这里的乘仍然指的是
对于本题来说,我们可以发现是多次查询没有修改,所以也可以考虑使用猫树,使查询复杂度变为
当然用平衡树,分块之类的也能做,这里不再过多赘述。
这里提供一个 DDP + 猫树 实现的 GSS1。(注意猫树节点数需要补齐到
(篇幅原因这里的代码省略了,可以从文章头的链接到原 Blog 访问查看)
题面:给定长度为 0
,1
,?
的字符串 ?
需任意替换为 0
或 1
。
我们先不考虑修改,思考对于这样一个字符串能有多少种子串,显然这个东西是个 DP。
令
若
若
若
应该不难理解吧?如果状态里是当前这一位,那么可以接到上一个状态任意的结尾,接上这一位之后都会是一个符合要求的新串,或者丢弃以前的直接让这一位成为一个新串。反之就直接由上一次的转移而来,把这一位丢弃,而
这里有一个细节可以解释一下,在这一位和状态相同的时候我们会
这么一大坨分类讨论显然很难维护,那么我们尝试把这些缩到一起:
关于式子中的方括号括起来的表达式,这个一般用来表示如果里面的表达式为
,那么这个东西值为 ,反之为 。
现在,我们就可以对这个简洁的式子搞事情了,因为我们后面要有很多次修改,这样的话显然可以考虑 DDP。
我们再次尝试设计出转移的矩阵:
不难算出:不难算出我们算不出来。
于是考虑再加一维
然后尝试推一下
(不要问我为什么这题用不到广义矩乘还要在前面说一大堆广义矩乘的内容,我记错了,技多不压身)
然后类比之前的过程,令:
那么一直拆下去,一定有:
依然尝试找到一个这样的等式:
所以可以最终化为:
终于推完了,不难发现我们每次的修改就是
这玩意有修改显然用不了猫树了,老老实实写线段树吧。。
复杂度大概是
xxxxxxxxxx
1171
2
3
4
5
6
7
8
9
10
11using namespace std;
12using namespace __gnu_pbds;
13
14mt19937 rnd(random_device{}());
15int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
16bool rnddd(int x){return rndd(1, 100) <= x;}
17
18typedef unsigned int uint;
19typedef unsigned long long unll;
20typedef long long ll;
21typedef long double ld;
22
23
24
25
26template< typename T = int >
27inline T read(void);
28
29int N, Q;
30int S[MAXN];
31
32struct Matrix3{
33 int val[3][3];
34 Matrix3(int v00, int v01, int v02, int v10, int v11, int v12, int v20, int v21, int v22):
35 val{
36 {v00, v01, v02},
37 {v10, v11, v12},
38 {v20, v21, v22}
39 }{;}
40 Matrix3(int S):
41 val{
42 {1, S != 0, 0},
43 {S != 1, 1, 0},
44 {S != 1, S != 0, 1}
45 }{;}
46 Matrix3(int val[][3]){for(int i = 0; i <= 2; ++i)for(int j = 0; j <= 2; ++j)this->val[i][j] = val[i][j];}
47 Matrix3(void) = default;
48 friend const Matrix3 operator * (const Matrix3 &x, const Matrix3 &y){
49 int val[3][3]; memset(val, 0, sizeof val);
50 for(int i = 0; i <= 2; ++i)for(int j = 0; j <= 2; ++j)for(int p = 0; p <= 2; ++p)
51 val[i][j] = ((ll)val[i][j] + (ll)x.val[i][p] * y.val[p][j] % MOD) % MOD;
52 return Matrix3(val);
53 }
54 void Print(void){
55 for(int i = 0; i <= 2; ++i)for(int j = 0; j <= 2; ++j)
56 printf("%d%c", val[i][j], j == 2 ? '\n' : ' ');
57 }
58}mt[MAXN];
59
60class SegTree{
61private:
62 Matrix3 tr[MAXN << 2];
63
64
65
66public:
67 void Pushup(int p){tr[p] = tr[LS] * tr[RS];}
68 void Build(int p = 1, int gl = 1, int gr = N){
69 if(gl == gr)return tr[p] = mt[gl = gr], void();
70 Build(LS, gl, MID);
71 Build(RS, MID + 1, gr);
72 Pushup(p);
73 }
74 void Modify(int idx, Matrix3 v, int p = 1, int gl = 1, int gr = N){
75 if(gl == gr)return tr[p] = v, void();
76 if(idx <= MID)Modify(idx, v, LS, gl, MID);
77 else Modify(idx, v, RS, MID + 1, gr);
78 Pushup(p);
79 }
80 Matrix3 Query(void){return tr[1];}
81}st;
82
83int main(){
84 N = read(), Q = read();
85 string s; cin >> s;
86 for(int i = 1; i <= (int)s.size(); ++i)
87 S[i] = s.at(i - 1) == '?' ? -1 : int(s.at(i - 1) - '0'),
88 mt[i] = Matrix3(S[i]);
89 st.Build();
90 Matrix3 origin(0, 0, 1, 0, 0, 0, 0, 0, 0);
91 while(Q--){
92 int p = read();
93 char c = getchar(); while(c != '0' && c != '1' && c != '?')c = getchar();
94 int flag = c == '?' ? -1 : int(c - '0');
95 st.Modify(p, Matrix3(flag));
96 auto ans = origin * st.Query();
97 printf("%d\n", (int)((ll)(ans.val[0][0] + ans.val[0][1]) % MOD));
98 }
99 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
100 return 0;
101}
102
103template < typename T >
104inline T read(void){
105 T ret(0);
106 short flag(1);
107 char c = getchar();
108 while(c != '-' && !isdigit(c))c = getchar();
109 if(c == '-')flag = -1, c = getchar();
110 while(isdigit(c)){
111 ret *= 10;
112 ret += int(c - '0');
113 c = getchar();
114 }
115 ret *= flag;
116 return ret;
117}
后面对于 DDP 更详尽的讲解可以 戳此进入。
update-2022_10_22 初稿