重新學習了一遍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,如果正向去解決這個問題有點困難,那麼我們可以從反面去考慮這個問題,即計算不滿足這個性質的字符串個數。
記錄爲匹配到第個字符,當前落在自動機號節點上面的不滿足條件的字符串個數。
那麼答案顯然就是
#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函數這題沒有用到,具體用起來也是一樣,不過好像是用完就刪除的,如果要多次查詢需要修改一下。