当前位置 : 主页 > 编程语言 > 其它开发 >

P7739 [NOI2021] 密码箱

来源:互联网 收集:自由互联 发布时间:2022-05-30
P7739 [NOI2021] 密码箱 题目链接: Link 本题解主要思路来源 @Rikka__(疯狂 % )。 考虑题里面的贡献的计算其实是一个连分数的倒数,即: \[a_1+\frac {1}{a_2+\frac{1}{a_3 + ...}}\] 若从 \(a_k\) 到 \(a_
P7739 [NOI2021] 密码箱

题目链接: Link

本题解主要思路来源 @Rikka__(疯狂 % )。

考虑题里面的贡献的计算其实是一个连分数的倒数,即:

\[a_1+\frac {1}{a_2+\frac{1}{a_3 + ...}} \]

若从 \(a_k\)\(a_1\) 从下向上合并的话能够发现其实是一个 $ \Large \frac{a'}{b'} = \frac {1}{a_i+\frac{a}{b}}$ 的形式。(首先根据辗转相除法的那一套理论明显是不用考虑约分)

那么考虑将 \(a_i\) 合并上后对答案的影响:

\[\frac {a'}{b'} = \frac{1}{a_i+\frac{a}{b}} = \frac {b}{a_i \times b + a} \]

那么也就是 \(a' = b ,b' = a_i \times b + a\)

转移是固定的故可以考虑使用矩阵进行转移:

\[\left[ \begin {array}{l} a'\\ b' \end{array} \right] = \left[ \begin {array}{l} 0 &1\\ 1&a_i \end{array} \right] \left[ \begin {array}{l} a\\ b \end{array} \right] \]

故答案即为:

\[\left[ \begin {array}{l} 0&1\\ 1&a_1 \end{array} \right] \left[ \begin {array}{l} 0&1\\ 1&a_2 \end{array} \right] ... \left[ \begin {array}{l} 0&1\\ 1&a_k \end{array} \right] \left[ \begin {array}{l} 0\\ 1 \end{array} \right] \]

继续考虑使用矩阵表述操作 WE

W

\(a_k \rightarrow a_{k}+1\)

发现矩阵从 \(\left[\begin {array}{l}0&1\\1&a_k\end{array}\right]\) 变成 \(\left[\begin{array}{l} 0&1\\1&a_{k}+1 \end{array} \right ]\) 可以发现转移矩阵即为:\(\left[\begin{array}{l} 1&1\\0&1\end{array}\right]\) (右乘)

E

\(a_k>1\)

\(a_k \rightarrow a_k - 1\) 并添加两个 \(1\) 到末尾。

按照上面的思路,发现 \(a_k \rightarrow a_k - 1\) 转移矩阵即为 \(\left[\begin{array}{l} 1&-1\\0&1\end{array}\right]\)

末尾添加两个 \(1\) 转移矩阵为 \(\left[\begin{array}{l}0&1\\1&1\end{array}\right]\left[\begin{array}{l}0&1\\1&1\end{array}\right]\)

按顺序整合一下即为 \(\left[\begin{array}{l}0&-1\\1&2\end{array}\right]\)

\(a_k = 1\)

这时 \(a_k\) 对应的矩阵为 \(\left[\begin{array}{l}0&1\\1&1\end{array}\right]\)

按照正常的运算应该为 \(\left[\begin{array}{l}0&1\\1&a_{k-1}\end{array}\right]\left[\begin{array}{l}1&1\\0&1\end{array}\right]\left[\begin{array}{l}0&1\\1&1\end{array}\right]\)

按照结合律可变为 \(\left[\begin{array}{l}0&1\\1&a_{k-1}\end{array}\right]\left[\begin{array}{l}1&2\\1&1\end{array}\right]\)

我们同样设一个矩阵 \(\left[\begin{array}{l}x&y\\z&k\end{array}\right]\)

满足:

\[\left[\begin{array}{l}0&1\\1&a_{k-1}\end{array}\right]\left[\begin{array}{l}0&1\\1&1\end{array}\right]\left[\begin{array}{l}x&y\\z&k\end{array}\right] = \left[\begin{array}{l}0&1\\1&a_{k-1}\end{array}\right]\left[\begin{array}{l}1&2\\1&1\end{array}\right] \]

解得矩阵 \(\left[\begin{array}{l}0&-1\\1&2\end{array}\right]\)

故对于 E 操作只需要乘上 \(\left[\begin{array}{l}0&-1\\1&2\end{array}\right]\)

剩余部分比较简单,只需要用一颗支持序列翻转、取反操作的平衡树实现即可。具体维护 区间连乘积区间取反连乘积区间翻转连乘积区间取反翻转连乘积。同时维护取反标记翻转标记即可

时间复杂度:\(O(n\log n)\)

优化:

其实有时空复杂度都更优的写法,考虑对于 REVERSEFLIP 操作也像 W E 那样去通过矩阵进行转移,具体的通过矩阵中 \(4\) 个元素的线性组合去构造转移矩阵。

设矩阵 $A = \left[\begin{array}{l} a&b \ c&d \end{array}\right] $。

有:

\[Flip(A) =\left[\begin{array}{l} a-b&-b \\ -a+b-c+d&b+d \end{array}\right]\\ \space \\\space \\ Reverse(A) = \left[\begin{array}{l} d-2c&-2a+b-4c+2d \\ c&a+2c \end{array}\right] \]

这样就可以不去维护 \(4\) 种乘积,空间和时间得到巨大优化。可以通过解方程来证明。

//editor : DRYAYST
//Wo shi ge da SHA BI
#include<bits/stdc++.h>
#define g() getchar()
#define il inline
#define ull unsigned long long
#define eps 1e-10
#define ll long long
#define pa pair<int, int>
#define for_1(i, n) for(int i = 1; i <= (n); ++i)
#define for_0(i, n) for(int i = 0; i < (n); ++i)
#define for_xy(i, x, y) for(int i = (x); i <= (y); ++i)
#define for_yx(i, y, x) for(int i = (y); i >= (x); --i)
#define for_edge(i, x) for(int i = head[x]; i; i = nxt[i])
#define int long long
#define DB double
#define ls tr[x].l
#define rs tr[x].r
#define m_p make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e6 + 10, INF = 0x7f7f7f7f, mod = 998244353;
il int qpow(int x, int k) {int ans = 1; while(k) {if(k & 1) ans = ans * x % mod; x = x * x % mod; k >>= 1; } return ans; }
il int Add(int x, int y) {return (x += y) %= mod;}
il int Del(int x, int y) {return (x = x - y + mod) % mod;}
il int Mul(int x, int y) {return x * y % mod;}
il int inv(int x) {return qpow(x, mod - 2); }
inline int re() {
    int x = 0, p = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') p = -1; ch = getchar();}
    while(ch <= '9' and ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    return x * p;
}
mt19937 rd(20050426);
int n, Q, rt, cnt ; 
char s[N], op[100]; 
struct Matrix {
    int a[2][2];  Matrix() {memset(a, 0, sizeof(a)); }
    il Matrix operator * (const Matrix &b) const {Matrix c;  for_0(k, 2) for_0(i, 2) for_0(j, 2) c.a[i][j] = (c.a[i][j] + a[i][k] * b.a[k][j] % mod) % mod;  return c;}
    il Matrix Rev(Matrix A) {Matrix c;
        c.a[0][0] = (A.a[1][1] - 2 * A.a[1][0] % mod + mod) % mod;  c.a[0][1] = ((-2 * A.a[0][0] + A.a[0][1] - 4 * A.a[1][0] + 2 * A.a[1][1]) % mod + mod) % mod; 
        c.a[1][0] = A.a[1][0];  c.a[1][1] = (A.a[0][0] + 2 * A.a[1][0] % mod) % mod;  return c; 
    }
    il Matrix Flip(Matrix A) { Matrix c; 
        c.a[0][0] = (A.a[0][0] - A.a[0][1] + mod) % mod;  c.a[0][1] = (-A.a[0][1] + mod) % mod; 
        c.a[1][0] = ((-A.a[0][0] + A.a[0][1] - A.a[1][0] + A.a[1][1]) % mod + mod) % mod;   c.a[1][1] = (A.a[0][1] + A.a[1][1]) % mod; return c; 
    }
}A, B, ans, use; //A,B分别为两个转移矩阵
struct Tree {int l, r; int val, siz, rtag, ftag; Matrix V, S; }tr[N]; 
il int Newed(int x) { int id = ++cnt; tr[id].V = (x==0?A:B); tr[id].S = tr[id].V;  tr[id].l = tr[id].r = 0; tr[id].siz = 1; tr[id].rtag = tr[id].ftag = 0; tr[id].val = rd(); return id; }
il void push_up(int x) {if(!x) return; tr[x].siz = tr[ls].siz + tr[rs].siz + 1; tr[x].S = tr[ls].S * tr[x].V * tr[rs].S; return; }
il void push_rdown(int x) { if(!x) return ;  tr[x].S = use.Rev(tr[x].S);  swap(tr[x].l, tr[x].r); tr[x].rtag ^= 1; }
il void push_fdown(int x) { if(!x) return ; tr[x].S = use.Flip(tr[x].S), tr[x].V = use.Flip(tr[x].V); tr[x].ftag ^= 1;  }
il void push_down(int x) { if(!x) return;  if(tr[x].rtag) push_rdown(tr[x].l), push_rdown(tr[x].r), tr[x].rtag = 0;  if(tr[x].ftag) push_fdown(tr[x].l), push_fdown(tr[x].r), tr[x].ftag = 0;  }
il int Merge(int k1, int k2) {
    if(!k1 || !k2) return k1 + k2; 
    if(tr[k1].val < tr[k2].val) {  push_down(k1); tr[k1].r = Merge(tr[k1].r, k2); push_up(k1); return k1; }
    else {push_down(k2); tr[k2].l = Merge(k1, tr[k2].l); push_up(k2); return k2; }
}
int Build(int l, int r) { 
    if(l == r) {int now = Newed(s[l] != 'W'); return now;} int mid = (l + r) >> 1; 
    int k1 = Build(l, mid), k2 = Build(mid + 1, r); return Merge(k1, k2);
}
il int Insert(int rt, int fl) {return Merge(rt, Newed(fl)); }
void Split(int rt, int x, int &k1, int &k2) { 
    if(!rt) {k1 = 0; k2 = 0; return ; } push_down(rt); 
    if(tr[tr[rt].l].siz < x) {k1 = rt; Split(tr[rt].r, x - tr[tr[rt].l].siz -1 , tr[k1].r, k2); push_up(k1); }
    else {k2 = rt; Split(tr[rt].l, x, k1, tr[k2].l); push_up(k2);}
}
il int Flip(int rt, int l, int r) {  int k1, k2, k3; Split(rt, r, k1, k3); Split(k1, l - 1, k1, k2); push_fdown(k2); return Merge(k1, Merge(k2, k3));  }
il int Reverse(int rt, int l, int r) { int k1, k2, k3; Split(rt, r, k1, k3); Split(k1, l-1, k1, k2); push_rdown(k2); return Merge(k1, Merge(k2, k3)); }
signed main() {
    n = re(), Q = re(); scanf("%s", s + 1); A.a[0][0] = 1; A.a[0][1] = 1; A.a[1][1] = 1; B.a[0][1] = mod - 1; B.a[1][0] = 1; B.a[1][1] = 2; 
    tr[0].V.a[0][0] = 1; tr[0].V.a[1][1] = 1; tr[0].S = tr[0].V; rt = Build(1, n);  ans = A * tr[rt].S; 
    printf("%lld %lld\n", ans.a[1][1], ans.a[0][1]); 
    while(Q--) {
        scanf("%s", op + 1); 
        if(op[1] == 'A') { scanf("%s", op + 1); rt = Insert(rt, op[1] != 'W'); }
        else if(op[1] == 'F') {int l = re(), r = re(); rt = Flip(rt, l, r); }
        else {int l = re(), r = re(); rt = Reverse(rt, l, r); }
        ans = A * tr[rt].S; printf("%lld %lld\n", ans.a[1][1], ans.a[0][1]); 
    }
}

可能代码有点不能看,建议 loj 格式化后观看更清晰。

网友评论