重新学习了一遍AC自动机,之前也没有系统性地学习过,现在看起来并不是那么困难,就是在字典树上面加了一个类似KMP的跳跃数组。
下面先上AC自动机的板子。
#include <bits/stdc++.h>
#define rep( i , l , r ) for( int i = (l) ; i <= (r) ; ++i )
#define per( i , r , l ) for( int i = (r) ; i >= (l) ; --i )
#define erep( i , u ) for( int i = head[(u)] ; ~i ; i = e[i].nxt )
using namespace std;
const int maxn = 1111;
struct AC{
int tr[maxn][26] , cnt;
int e[maxn];
int fail[maxn];
void ins( char *s ){
int u = 0;
for( int i = 0 ; s[i] ; ++i ){
int t = s[i] - 'a';
if( !tr[u][t] ) tr[u][t] = ++cnt;
u = tr[u][t];
}
e[u]++;
}
void calc_fail(){
queue<int> q;
memset( fail , 0 , sizeof fail );
for( int i = 0 ; i < 26 ; ++i ) if( tr[0][i] ) q.push( tr[0][i] );
while( !q.empty() ){
int u = q.front(); q.pop();
for( int i = 0 ; i < 26 ; ++i ){
if( tr[u][i] ){
fail[tr[u][i]] = tr[fail[u]][i]; // fail计算
q.push( tr[u][i] );
}else{
tr[u][i] = tr[fail[u]][i];
//匹配到空字符,则索引到父节点fail指针对应的字符,以供后续指针的构建
//类似并差集的路径压缩,把不存在的tr[k][i]全部指向tr[fail[k]][i]
//这句话在后面匹配主串的时候也能帮助跳转
}
}
}
}
int query( char *t ){
int u = 0 , res = 0;
for( int i = 0 ; t[i] ; ++i ){
u = tr[u][t[i] - 'a'];
for(int j = u ; j && ~e[j] ; j = fail[j] ) res += e[j] , e[j] = -1;
}
return res;
}
};
int main(){
return 0;
}
题目大意
给你T组数据,每组包含两个正整数N,M以及字符串T,问你有多少个长度为M的字符串S,满足T在最多修改一位的情况下可以成为S的子串。
思路和分析
下午这道题搞了好久,思路是AC自动机+DP,如果正向去解决这个问题有点困难,那么我们可以从反面去考虑这个问题,即计算不满足这个性质的字符串个数。
记录
f
[
i
]
]
[
j
]
f[i]][j]
f[i]][j]为匹配到第
i
i
i个字符,当前落在自动机
j
j
j号节点上面的不满足条件的字符串个数。
那么答案显然就是
2
M
−
Σ
i
=
1..
a
c
.
c
n
t
f
[
M
]
[
i
]
2^M-\Sigma_{i=1..ac.cnt} f[M][i]
2M−Σi=1..ac.cntf[M][i]
#include <bits/stdc++.h>
#define rep( i , l , r ) for( int i = (l) ; i <= (r) ; ++i )
#define per( i , r , l ) for( int i = (r) ; i >= (l) ; --i )
#define erep( i , u ) for( int i = head[(u)] ; ~i ; i = e[i].nxt )
using namespace std;
const int maxn = 2222;
struct AC{
int tr[maxn][26] , cnt;
int e[maxn];
int fail[maxn];
void ins( char *s ){
int u = 0;
for( int i = 0 ; s[i] ; ++i ){
int t = s[i] - '0';
if( !tr[u][t] ) tr[u][t] = ++cnt;
u = tr[u][t];
}
e[u]++;
}
void calc_fail(){
queue<int> q;
memset( fail , 0 , sizeof fail );
for( int i = 0 ; i < 26 ; ++i ) if( tr[0][i] ) q.push( tr[0][i] );
while( !q.empty() ){
int u = q.front(); q.pop();
for( int i = 0 ; i < 26 ; ++i ){
if( tr[u][i] ){
fail[tr[u][i]] = tr[fail[u]][i]; // fail计算
q.push( tr[u][i] );
}else{
tr[u][i] = tr[fail[u]][i];
//匹配到空字符,则索引到父节点fail指针对应的字符,以供后续指针的构建
//类似并差集的路径压缩,把不存在的tr[k][i]全部指向tr[fail[k]][i]
//这句话在后面匹配主串的时候也能帮助跳转
}
}
}
}
int query( char *t ){
int u = 0 , res = 0;
for( int i = 0 ; t[i] ; ++i ){
u = tr[u][t[i] - '0'];
for(int j = u ; j && ~e[j] ; j = fail[j] ) res += e[j] , e[j] = -1;
}
return res;
}
}ac;
typedef long long ll;
ll f[44][maxn];
char str[111];
ll qpow( ll x , int y ){
ll res = 1ll;
for( ; y ; y >>= 1 ){
if( y & 1 ) res *= x;
x = x * x;
}
return res;
}
int main(){
int T = 0;
scanf("%d" , &T);
while( T-- ){
int N , M;
ac.cnt = 0;
scanf("%d%d%s" , &N , &M , str );
memset( ac.tr , 0 , sizeof ac.tr );
memset( ac.e , 0 , sizeof ac.e );
memset( f , 0 , sizeof f );
ac.ins( str );
for( int i = 0 ; i < N ; ++i ){
str[i] ^= 1;
ac.ins( str );
str[i] ^= 1;
}
ac.calc_fail();
f[0][0] = 1;
for( int i = 0 ; i < M ; ++i ){
for( int j = 0 ; j <= ac.cnt ; ++j ){
if( f[i][j] == 0 || ac.e[j] ) continue;
for( int k = 0 ; k < 2 ; ++k ){
int u = j;
while( ac.tr[u][k] == 0 ) u = ac.fail[u];
if( ac.e[ac.tr[u][k]] > 0 ) continue;
f[i + 1][ac.tr[u][k]] += f[i][j];
}
}
}
ll ans = qpow( 2ll , M );
for( int i = 0 ; i <= ac.cnt ; ++i ){
ans -= f[M][i];
}
cout << ans << endl;
}
}
需要注意这个板子有一个虚根0,注意从虚根开始统计,同时要注意cnt开始为0。
query函数这题没有用到,具体用起来也是一样,不过好像是用完就删除的,如果要多次查询需要修改一下。