\(\mathcal{Description}\)

  Link.

  有一个状态集为 \(V\) 的自动机,状态接收 (, )_(空格) 三种字符,分别编号为 \(0,1,2\),状态 \(u\) 的 \(i\) 转移指向状态 \(d_{u,i}\),方案数为 \(e_{u,i}\)。求从 \(s\) 出发到 \(t\) 终止能接受的长度恰好为 \(n\) 的字符串中,忽略空格后正则匹配的字符串数量。模 \(998244353\)。

  \(|V|\le2\),\(n\le10^5\)。

\(\mathcal{Solution}\)

  回忆一下 Catalan 数列的递推求法,我们发现暴力 DP 容易写成矩阵乘法的形式。令 \(F_i=\begin{bmatrix}f_{i,0,0}&f_{i,0,1}\\f_{i,1,0}&f_{i,1,1}\end{bmatrix}\) 表示转移 \(i\) 步,从 \(s\) 走到 \(t\) 能形成的匹配串数量。设 \(E_0,E_1,E_2\) 分别表示选择 (, )_ 的转移矩阵,那么

\[F_i=\sum_{j=0}^{i-2}F_jE_0F_{i-j-2}E_1+F_{i-1}E_2
\]

发现和式是卷积形式,而矩阵系数的多项式显然适用于 FFT。所以直接 CDQ 分治(或许叫分治 FFT?)就能算出所有 \(F\)。实现细节比较多,复杂度 \(\mathcal O(|V|^2n\log^2n+|V|^3n\log n)\)。

\(\mathcal{Code}\)

  忙着补题,所以实现得粗糙,常数比较大 qwq。

/*~Rainybunny~*/

#include <cstdio>
#include <cassert>
#include <iostream>
#include <algorithm> #define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i ) const int MAXN = 1e5, MAXL = 1 << 18, MOD = 998244353, MG = 3; inline int imin( const int a, const int b ) { return a < b ? a : b; }
inline int imax( const int a, const int b ) { return a < b ? b : a; }
inline void subeq( int& a, const int b ) { ( a -= b ) < 0 && ( a += MOD ); }
inline int sub( int a, const int b ) { return ( a -= b ) < 0 ? a + MOD : a; }
inline int mul( const long long a, const int b ) { return int( a * b % MOD ); }
inline int add( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
inline void addeq( int& a, const int b ) { ( a += b ) >= MOD && ( a -= MOD ); }
inline int mpow( int a, int b ) {
int ret = 1;
for ( ; b; a = mul( a, a ), b >>= 1 ) ret = mul( ret, b & 1 ? a : 1 );
return ret;
} struct Matrix {
int mat[2][2];
inline int* operator [] ( const int k ) { return mat[k]; }
inline Matrix operator * ( const int k ) const {
return Matrix
{ { { mul( mat[0][0], k ), mul( mat[0][1], k ) },
{ mul( mat[1][0], k ), mul( mat[1][1], k ) } } };
}
inline Matrix operator + ( const Matrix& t ) const {
return Matrix
{ { { add( mat[0][0], t.mat[0][0] ), add( mat[0][1], t.mat[0][1] ) },
{ add( mat[1][0], t.mat[1][0] ), add( mat[1][1], t.mat[1][1] ) } } };
}
inline Matrix operator - ( const Matrix& t ) const {
return Matrix
{ { { sub( mat[0][0], t.mat[0][0] ), sub( mat[0][1], t.mat[0][1] ) },
{ sub( mat[1][0], t.mat[1][0] ), sub( mat[1][1], t.mat[1][1] ) } } };
}
inline Matrix operator * ( const Matrix& t ) const {
static Matrix ret;
ret[0][0] = ret[0][1] = ret[1][0] = ret[1][1] = 0;
rep ( i, 0, 1 ) rep ( k, 0, 1 ) rep ( j, 0, 1 ) {
addeq( ret[i][j], mul( mat[i][k], t.mat[k][j] ) );
}
return ret;
}
inline void clear() { mat[0][0] = mat[0][1] = mat[1][0] = mat[1][1] = 0; }
}; namespace PolyOper { int omega[20][MAXL + 5]; inline void init() {
rep ( i, 0, 18 ) {
int* oi = omega[i];
oi[0] = 1, oi[1] = mpow( MG, MOD - 1 >> i >> 1 );
rep ( j, 2, ( 1 << i ) - 1 ) oi[j] = mul( oi[j - 1], oi[1] );
}
} inline void ntt( const int len, Matrix* u, const int type ) {
static int rev[MAXL + 5];
int tlg = 0;
for ( ; 1 << tlg < len; ++tlg );
rep ( i, 1, len - 1 ) rev[i] = rev[i >> 1] >> 1 | ( i & 1 ) << tlg >> 1;
rep ( i, 0, len - 1 ) if ( i < rev[i] ) std::swap( u[i], u[rev[i]] ); for ( int i = 0, stp = 1; stp < len; ++i, stp <<= 1 ) {
int* oi = omega[i];
for ( int j = 0; j < len; j += stp << 1 ) {
rep ( k, j, j + stp - 1 ) {
Matrix ev( u[k] ), ov( u[k + stp] * oi[k - j] );
u[k] = ev + ov, u[k + stp] = ev - ov;
}
}
} if ( !~type ) {
int il = mpow( len, MOD - 2 );
rep ( i, 0, len - 1 ) u[i] = u[i] * il;
std::reverse( u + 1, u + len );
}
} } // namespace PolyOper. Matrix E[3], F[MAXN + 5]; inline void solve( const int l, const int r ) {
if ( l == r ) return ;
int mid = l + r >> 1; solve( l, mid ); static Matrix T[2][MAXL + 5];
int len = 1;
for ( ; len <= r - l + 2 << 1; len <<= 1 ); rep ( i, l, mid ) T[0][i - l] = F[i] * E[0];
rep ( i, 0, imin( l - 1, r - l - 2 ) ) T[1][i] = F[i] * E[1];
PolyOper::ntt( len, T[0], 1 ), PolyOper::ntt( len, T[1], 1 );
rep ( i, 0, len - 1 ) T[0][i] = T[0][i] * T[1][i], T[1][i].clear();
PolyOper::ntt( len, T[0], -1 );
rep ( i, imax( mid + 1, 2 ), r ) F[i] = F[i] + T[0][i - l - 2];
rep ( i, 0, len - 1 ) T[0][i].clear(); rep ( i, 0, r - l - 2 ) T[0][i] = F[i] * E[0];
rep ( i, l, mid ) T[1][i - l] = F[i] * E[1];
PolyOper::ntt( len, T[0], 1 ), PolyOper::ntt( len, T[1], 1 );
rep ( i, 0, len - 1 ) T[0][i] = T[0][i] * T[1][i], T[1][i].clear();
PolyOper::ntt( len, T[0], -1 );
rep ( i, imax( mid + 1, 2 ), r ) F[i] = F[i] + T[0][i - l - 2];
rep ( i, 0, len - 1 ) T[0][i].clear(); F[mid + 1] = F[mid + 1] + F[mid] * E[2];
solve( mid + 1, r );
} int main() {
freopen( "dfa.in", "r", stdin );
freopen( "dfa.out", "w", stdout ); int V; scanf( "%d", &V );
rep ( i, 0, V - 1 ) {
int d0, e0, d1, e1, d2, e2;
scanf( "%d %d %d %d %d %d", &d0, &e0, &d1, &e1, &d2, &e2 );
E[0][i][d0] = e0, E[1][i][d1] = e1, E[2][i][d2] = e2;
} PolyOper::init();
F[0][0][0] = F[0][1][1] = 1, solve( 0, MAXN ); int q, s, t, n; scanf( "%d", &q );
while ( q-- ) {
scanf( "%d %d %d", &s, &t, &n );
printf( "%d\n", F[n][s][t] );
}
return 0;
}

Solution -「多校联训」自动机的更多相关文章

  1. Solution -「多校联训」排水系统

    \(\mathcal{Description}\)   Link.   在 NOIP 2020 A 的基础上,每条边赋权值 \(a_i\),随机恰好一条边断掉,第 \(i\) 条段的概率正比于 \(a ...

  2. Solution -「多校联训」I Love Random

    \(\mathcal{Description}\)   给定排列 \(\{p_n\}\),可以在其上进行若干次操作,每次选取 \([l,r]\),把其中所有元素变为原区间最小值,求能够得到的所有不同序 ...

  3. Solution -「多校联训」签到题

    \(\mathcal{Description}\)   Link.   给定二分图 \(G=(X\cup Y,E)\),求对于边的一个染色 \(f:E\rightarrow\{1,2,\dots,c\ ...

  4. Solution -「多校联训」朝鲜时蔬

    \(\mathcal{Description}\)   Link.   破案了,朝鲜时蔬 = 超现实树!(指写得像那什么一样的题面.   对于整数集 \(X\),定义其 好子集 为满足 \(Y\sub ...

  5. Solution -「多校联训」消失的运算符

    \(\mathcal{Description}\)   Link.   给定长度为 \(n\) 的合法表达式序列 \(s\),其中数字仅有一位正数,运算符仅有 - 作为占位.求将其中恰好 \(k\) ...

  6. Solution -「多校联训」假人

    \(\mathcal{Description}\)   Link.   一种物品有 长度 和 权值 两种属性,现给定 \(n\) 组物品,第 \(i\) 组有 \(k_i\) 个,分别为 \((1,a ...

  7. Solution -「多校联训」古老的序列问题

    \(\mathcal{Description}\)   Link.   给定序列 \(\{a_n\}\),和 \(q\) 次形如 \([L,R]\) 的询问,每次回答 \[\sum_{[l,r]\su ...

  8. Solution -「多校联训」Sample

    \(\mathcal{Description}\)   Link   (稍作简化:)对于变量 \(p_{1..n}\),满足 \(p_i\in[0,1],~\sum p_i=1\) 时,求 \(\ma ...

  9. Solution -「多校联训」光影交错

    \(\mathcal{Description}\)   Link.   一个游戏包含若干次卡牌抽取,每次以 \(p_l\) 的概率得到 \(+1\),\(p_d\) 的概率得到 \(-1\),否则得到 ...

随机推荐

  1. vue中computed的作用以及用法

    在vue中computed是计算属性,主要作用是把数据存储到内存中,减少不必要的请求,还可以利用computed给子组件的data赋值. 参考地址:https://www.jianshu.com/p/ ...

  2. 通过js触发launch事件获取页面信息

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6814776265602499080/ 承接上一篇文档<页面Cookie的JS文件编写> 思路 继续之前 ...

  3. js获取相邻节点的value值

    document.getElementById('id').nextElementSibling.value或者document.getElementById('id').previousElemen ...

  4. SYCOJ27猴子选大王

    题目-猴子选大王 (shiyancang.cn) 一.出队顺序Description有M个人,其编号分别为1-M.这M个人按顺序排成一个圈.现在给定一个数N,从第一个人开始依次报数,数到N的人出列,然 ...

  5. SYCOJ246螺旋矩阵

    题目-螺旋矩阵 (shiyancang.cn) noip201403螺旋矩阵[普及组]数学算法 - 大本营 - 博客园 (cnblogs.com) 以下为搬运代码.一个为算圈数,另外一个是数学方法 思 ...

  6. BeanUtils.copyProperties 选择性赋值字段

    BeanUtils.copyProperties 在字段赋值上有强大的功能,如果有两个的类,如果需要将相同的字段赋值,就可以直接赋制.而不需要每个字段都需要一个一个赋制. BeanUtils.copy ...

  7. 使用.NET 6开发TodoList应用(30)——实现Docker打包和部署

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 .NET 6 Web API应用使用最多的场景是作为后端微服务应用,在实际的项目中,我们一般都是通过将应用程序打包成docke ...

  8. elasticsearch拼写纠错之Term Suggester

    一.什么是拼写纠错 拼写纠错就是搜索引擎可以智能的感知用户输入关键字的错误,并使用纠正过的关键字进行搜索展示给用户:拼写纠错是一种改善用户体验的功能: elasticsearch提供了以下不同类型的s ...

  9. Solon Web 开发,四、请求上下文

    Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...

  10. 515. Find Largest Value in Each Tree Row

    You need to find the largest value in each row of a binary tree. Example: Input: 1 / \ 3 2 / \ \ 5 3 ...