模拟64—「三元组·简单的字符串·环路·过河」
三元组
比较简单的扫描线,枚举 \(c\) 的位置,用树状数组维护一下 \(a,b\) 的数对有多少对。
因为可以取等所以先加入在统计答案,考场define int long long
,被卡常了。
简单的字符串
\(\rm nb\) 题,让我入门了 \(\rm border\) 理论。
但发现自己有个结论不会证明,所以挖个坑。
放个链接,有时间要仔细读一下 link
填坑 upd 9.29(因为颓爆了(以后颓爆果断直接补博客))
首先让可以把循环同构串 \(A,B\) 拆分一下,肯定存在一种拆分方式使得 \(A=uv,B=vu\)
现在我来证明一个结论:
“ \(u\) 是 \(AB\) 的长度小于等于一半的最长 \(\rm border\) ”或者“\(v\) 是 \(BA\) 的长度小于等于一半的最长 \(\rm border\) 至少有一个为真 ”
其实等价于证明 \(u\) 不是 \(AB\) 的长度小于等于一半的最长 \(\rm border\) 的时候,\(v\) 一定是 \(BA\) 的长度小于等于一半的最长 \(\rm border\)
考虑\(u\) 不是 \(AB\) 的长度小于等于一半的最长 \(\rm border\)什么时候会发生。
当且仅当存在一个串 \(T\) , 使得 \(T\) 是 \(B\) 的 \(\rm border\) 并且 \(A+T=T+A\) 。
分类讨论 \(A\leq T\) 和 \(A>T\) ,发现一个可以表示成另一个的整周期,那么此时如果第二个也不满足,那么一定能找到一种重新的划分方式
(不想展开了,可以自己手模理解。)
所以第一种情况可以跳等差数列直接找到小于等于一半的(最多3次(两次找公差,一次跳到小于))。
第二种情况直接 \(hash\) 就可以。
环路
首先可以写出来一个简单的暴力。
设邻接矩阵为 \(A\) ,则 \(A^k[i][j]\) 的实际意义 \(i\) 通过恰好 \(k\) 步走到 \(j\) 的方案数(本质是 \(dp\) 的矩阵优化)。
那么找环就是看找能走到自己的方案数,也就是矩阵的主对角线上面数字的和。
也就是求出矩阵的连续幂次和,最后把主对角线上面的数字加起来。
可以发现可以用分治来做,把他分成 \(1-mid\) 和 \(mid+1 - r\) 两个部分,可以把 \(A^{mid}\) 提出来,然后问题变成了
\(A^{mid}*(1+solve(r>>1))\) 。
如果每次都快速幂一次,复杂度是 \(log^2\) 的,发现我们只需要计算一条快速幂链上面的值。
所以在分治的时候模拟快速幂就行了。
放一个 \(n^3logn\) 复杂度的代码(一共 \(\rm 800^+ ms\) )。
#include <bits/stdc++.h>
#define int long long
#define R register int
#define scanf Ruusupuu = scanf
#define freopen rsp_5u = freopen
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
int Ruusupuu ;
FILE * rsp_5u ;
using namespace std ;
typedef long long L ;
typedef double D ;
const int N = 1e2 + 2 ;
inline int read(){
int w = 0 ; bool fg = 0 ; char ch = getchar() ;
while( ch < '0' || ch > '9' ) ch = getchar() ;
while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ 48 ) , ch = getchar() ;
return fg ? -w : w ;
}
int n , K , P , ans , mat [N][N] , mmat [N][N] , po [N][N] , zc [N][N] ;
char s [N] ;
inline int A( int a , int b ){ return ( a + b ) >= P ? ( a + b - P ) : ( a + b ) ; }
inline int T( int a , int b ){ return ( 1ll * a * b ) - ( ( 1ll * a * b ) / P ) * P ; }
void sc(){
n = read() ;
for( R i = 1 ; i <= n ; i ++ ){
scanf( "%s" , s + 1 ) ;
for( R j = 1 ; j <= n ; j ++ ) mmat [i][j] = mat [i][j] = ( s [j] == 'Y' ) ;
} K = read() , P = read() ;
}
int b [N][N] , pp [N][N] ;
inline void mul( int a [N][N] , int c [N][N] ){
memset( b , 0 , sizeof( b ) ) ;
for( R k = 1 ; k <= n ; k ++ )for( R i = 1 ; i <= n ; i ++ ) {
if( !a [i][k] ) continue ;
for( R j = 1 ; j <= n ; j ++ )
b [i][j] = ( b [i][j] + ( a [i][k] * c [k][j] ) ) % P ;
} memcpy( a , b , sizeof( b ) ) ;
}
inline void mulself( int a [N][N] ){
memset( b , 0 , sizeof( b ) ) ;
for( R k = 1 ; k <= n ; k ++ )for( R i = 1 ; i <= n ; i ++ ) {
if( !a [i][k] ) continue ;
for( R j = 1 ; j <= n ; j ++ )
b [i][j] = ( b [i][j] + ( a [i][k] * a [k][j] ) ) % P ;
} memcpy( a , b , sizeof( b ) ) ;
}
inline void add( int a [N][N] , int b [N][N] ){ for( R i = 1 ; i <= n ; i ++ ) for( R j = 1 ; j <= n ; j ++ ) a [i][j] = A( a [i][j] , b [i][j] ) ; }
int lst [N][N] ;
void slv( int lef ){ // for( R i = 1 ; i <= lef ; i ++ ) ans += pow( mat , i ) ;
if( lef == 1 ) return memcpy( lst , mmat , sizeof( lst ) ) , void() ;
if( lef & 1 ){
slv( lef >> 1 ) ;
memcpy( zc , mat , sizeof( mat ) ) , mul( mat , lst ) , add( mat , zc ) ;
mulself( lst ) , mul( lst , mmat ) ;
add( mat , lst ) ;
} else{
slv( ( lef >> 1 ) ) ;
memcpy( zc , mat , sizeof( mat ) ) , mul( mat , lst ) , add( mat , zc ) ;
mulself( lst ) ;
}
}
void work(){
int tim = K - 1 ;
slv( tim ) ;
for( R i = 1 ; i <= n ; i ++ ) ans = A( ans , mat [i][i] ) ;
printf( "%lld\n" , ans ) ;
}
signed main(){ fre(tour);
sc() ;
work() ;
return 0 ;
}
过河
稍后
总结
- T1被卡常了,闲着没事还是多取模关掉
define int long long
不要偷懒了。 - T2数据没捆绑,没啥好说的。
- T3考试最后一分钟打完的,统计答案的时候没取模爆0了,OI宝典还是要熟记,打的时候就要取模。