2018Beijing H. Approximate Matching

重新學習了一遍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]爲匹配到第ii個字符,當前落在自動機jj號節點上面的不滿足條件的字符串個數。
那麼答案顯然就是2MΣi=1..ac.cntf[M][i]2^M-\Sigma_{i=1..ac.cnt} f[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函數這題沒有用到,具體用起來也是一樣,不過好像是用完就刪除的,如果要多次查詢需要修改一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章