[題解]P3311 [SDOI2014] 數數

Sinktank發表於2024-08-21

P3311 [SDOI2014] 數數

看到多模式匹配,我們考慮先對所有模式串建立AC自動機。

然後發現這道題和P4052 文字生成器題解)挺像的,後者讓求包含至少一個模式串的個數,這道題讓求一個也不包含的個數,這個就是一個用不用\(26^m\)去減的問題,很好處理。但這道題還多了一個條件,“幸運數”必須\(\le n\),而\(n\)足足有\(1200\)位,所以很自然地想到數位dp。

實際上,P4052也可以用數位dp來解決,只不過填寫沒有大小限制,每個節點都可以填A~Z。換到這道題上也一樣,只不過記憶化的前提是“當前非前導\(0\)”而且“填寫當前位無限制”。

下文中的“答案”均表示“不可讀的字串個數”。

\(f[pos][p]\)來表示“主串第\(pos\)個字元對應自動機上節點\(p\)”的答案。

數位dp提供\(4\)個引數:

  • int \(pos\):當前在主串的哪一位(從最高位開始,以\(pos=0\)為結束條件)。
  • int \(p\)\(當前pos\)對應自動機上哪個節點。
  • bool \(limit\):填寫是否受限(數位dp標配)。
  • bool \(zero\):是否是前導\(0\)(數位dp標配)。

\(f\)記憶化即可。

int dfs(int pos,int p,bool limit,bool zero){
	if(en[p]) return 0;//如果遇到字串結尾,則答案一定為0
	if(!pos) return !zero;//限定正整數
	if(!limit&&!zero&&~f[pos][p]) return f[pos][p];
	int rig=limit?a[pos]:9;
	int ans=0;
	for(int i=0;i<=rig;i++){
		int tzero=zero&&!i;
		ans=(ans+dfs(pos-1,tzero?0:tr[p][i],limit&&i==rig,tzero))%mod;
	}
	if(!limit&&!zero) f[pos][p]=ans;
	return ans;
}

時間複雜度\(O(\log_{10}n\times \sum|s|\times |\Sigma|)\)

點選檢視程式碼
#include<bits/stdc++.h>
#define mod 1000000007
#define N 1510//節點數(模式串總長)
#define M 1210//主串長度
#define C 10//字符集大小
using namespace std;
int n,m,tr[N][C],fail[N],cnt;
int q[N],head,tail,f[M][N],a[M];
bool en[N];
string s,t;
void ins(string s){
	int p=0;
	for(char i:s){
		int c=i-'0';
		if(!tr[p][c]) tr[p][c]=++cnt;
		p=tr[p][c];
	}
	en[p]=1;
}
void get_fail(){
	head=0,tail=-1;
	for(int i=0;i<C;i++) if(tr[0][i]) q[++tail]=tr[0][i];
	while(head<=tail){
		int u=q[head++];
		for(int i=0;i<C;i++){
			if(tr[u][i]) fail[tr[u][i]]=tr[fail[u]][i],q[++tail]=tr[u][i],
				en[tr[u][i]]|=en[fail[tr[u][i]]];
			else tr[u][i]=tr[fail[u]][i];
		}
	}
}
int dfs(int pos,int p,bool limit,bool zero){
	if(en[p]) return 0;
	if(!pos) return !zero;
	if(!limit&&!zero&&~f[pos][p]) return f[pos][p];
	int rig=limit?a[pos]:9;
	int ans=0;
	for(int i=0;i<=rig;i++){
		int tzero=zero&&!i;
		ans=(ans+dfs(pos-1,tzero?0:tr[p][i],limit&&i==rig,tzero))%mod;
	}
	if(!limit&&!zero) f[pos][p]=ans;
	return ans;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>s>>m;
	for(int i=1;i<=m;i++){
		cin>>t;
		ins(t);
	}
	get_fail();
	memset(f,-1,sizeof f);
	n=s.size();
	for(int i=0;i<n;i++) a[n-i]=s[i]-'0';
	cout<<dfs(n,0,1,1)<<"\n";
	return 0;
}

一個問題

好像發現了什麼……

題解區有些不用dfs,用陣列線性遞推答案的寫法,可以把\(f\)的第一維滾掉,因為第一維是最外層迴圈列舉的,且\(f[i]\)只和\(f[i-1]\)有關;然而dfs寫法應該是滾不掉的,因為一次跑到底,那麼每個\(f[i]\)都存在有用的值,所以不能滾。

解決數位dp一般首選dfs,但上面的空間問題,難道是dfs寫法的一個弊端嗎?能否解決這個問題?

想到了bfs之類的解決方案,但是還沒梳理出頭緒,網上也貌似完全沒有提到bfs實現的數位dp。

如果大家有任何想法請發在評論區,謝謝!

相關文章