1. 程式人生 > 其它 >NFLSOJ #10283 -「2019五校聯考-雅禮1」耍望節(按位貪心+dp)

NFLSOJ #10283 -「2019五校聯考-雅禮1」耍望節(按位貪心+dp)

題面傳送門

提供一種不同於官方題解的做法,考試最後 20min 想出來卻沒時間寫了。

下記 \(t\) 表示題面中的 \(m\)\(m=|t|\)

首先注意到 \(k\le 10^{18}\),​因此我們猜個結論,就是對於所有詢問,結果串中有很長一段字首都是相同的,只有最後若干個問號所對應的數是不同的。準確來說,從右往左數第 \(\lceil\log_{10}(k)+m\rceil=38\approx 40\) 個問號開始的字首都是相同的。因此我們會選擇按位在前 \(\min(c-40,0)\) 個問號處填上最小的數,滿足將這個問號替換為這個數之後,存在一種合法的填數方式,使得 \(t\) 為填好後的字串的子串

,其中 \(c\) 表示問號的個數。這是為什麼呢?因為由於存在一種合法的填數方式,因此肯定存在一段長度為 \(m\) 的子串 \(s[l…r]\),滿足這個子串與 \(t\) 匹配,這裡的匹配定義為帶萬用字元的匹配,即問號可以和任何字元匹配,非問號(也就是數字字元)只能與與其相同的字元匹配。這樣,我們可以在這個子串處按順序填上 \(t\) 中的字元,使得 \(t\) 出現在 \(s\) 中,假設這個子串中問號個數為 \(x\),那麼剩餘 \(40-x\) 個問號可以替換為任何數字,方案數就是 \(10^{40-x}\),而顯然 \(x\le|m|\le 20\),因此 \(10^{40-x}\ge 10^{20}\)
,遠超 \(k\) 的上限 \(10^{18}\)

這樣我們可以一遍預處理填上前 \(\min(c-40,0)\) 個問號處的數,具體方法就是貪心。維護 \(mch_i\) 表示 \(s[i…i+m-1]\) 能夠與 \(t\) 匹配多少位,以及 \(ok_i\) 表示開頭位置在 \(i\)\(n-m+1\),長度為 \(m\) 的子串中,是否存在某個子串能夠與 \(t\) 完全匹配。這樣我們可以在 \(\mathcal O(m)\) 的時間內判斷將一個問號替換為另一個字元後是否存在符合要求的填數方式。

在接下來的討論中我們先特判掉以下兩種情況:

  • 如果不論怎麼填都不存在合法的填數方式,全輸出 \(-1\)
    即可,由於是多測,別忘了把詢問讀完。
  • 如果不存在任何問號,那如果 \(k=1\) 直接輸出原數模 \(10^9+7\),否則輸出 \(-1\)​。

我們建出 \(t\) 的 fail 樹,顯然 fail 樹上的節點個數為 \(m+1\)。我們再預處理出 \(trs_{i,j}\) 表示從倒數第 \(i\) 個問號,當前在 \(fail\) 樹上第 \(j\) 個節點,從倒數第 \(i\) 個問號開始讀入字元直到倒數第 \(i-1\) 個問號後會到達 \(fail\) 樹上哪個節點,以及 \(can_{i,j}\) 表示從倒數第 \(i\) 個問號,當前在 \(fail\) 樹上第 \(j\) 個節點,從倒數第 \(i\) 個問號開始讀入字元直到倒數第 \(i-1\) 個問號後是否能夠與 \(t\) 匹配,預處理這兩個陣列之後就可以 DP 了。\(dp_{i,j,k}\) 表示目前確定了倒數第 \(i\) 個問號前的所有數,目前位於 fail 樹上的節點 \(j\),目前是否匹配過 \(t\) 的狀態為 \(k\),有多少種填上後 \(i\) 個問號的符合條件的填數方式,這顯然可以在 \(\mathcal O(40·10·m)\) 的時間內 DP 出來,預處理完 \(dp\)​ 後就可以按位貪心了,可以在 \(\mathcal O(40·10)\) 的時間內回答每個詢問。

總複雜度大概就是 \(\mathcal O(Dnm+D·p^2m^2+qpD)\),其中 \(D=10,p=40\)

好像和 djq 神仙思路撞了(

const int MAXM=20;
const int MAXN=1e5;
const int MOD=1e9+7;
const ll INF=0x3f3f3f3f3f3f3f3fll;
int n,qu,m,pw[MAXN+5];char t[MAXM+5],s[MAXN+5];
int mch[MAXN+5],pos[MAXN+5],pcnt=0;
bool ok[MAXN+5];
int ch[MAXM+5][11],fail[MAXM+5];
void build(){
	for(int i=1;i<=m;i++) ch[i-1][t[i]-'0']=i;
	queue<int> q;q.push(1);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=0;i<10;i++){
			if(ch[x][i]) fail[ch[x][i]]=ch[fail[x]][i],q.push(ch[x][i]);
			else ch[x][i]=ch[fail[x]][i];
		}
	}
}
bool match(char x,char y){return (x=='?'||x==y);}
bool calc_fst(){
	for(int i=1;i<=n-m+1;i++) for(int j=1;j<=m;j++)
		if(match(s[i+j-1],t[j])) mch[i]++;
	ok[n+1]=0;for(int i=n;i;i--) ok[i]=ok[i+1]|(mch[i]==m);
	int lim=pos[max(0,pcnt-40)],cur=0,flg=0;
	for(int i=1;i<=lim;i++){
		if(s[i]!='?'){
			cur=ch[cur][s[i]-'0'];
			flg|=(cur==m);
		} else {
			for(int j=0;j<10;j++){
				if(flg){s[i]=j+'0';break;}
				if(ok[min(i+1,n+1)]){s[i]=j+'0';break;}
				bool is=0;
				for(int k=1;k<=min(i,m);k++){
					if(mch[i-k+1]-match('?',t[k])+match(j+'0',t[k])==m){
						is=1;break;
					}
				} if(is){s[i]=j+'0';break;}
			} if(s[i]=='?') return 0;
			cur=ch[cur][s[i]-'0'];flg|=(cur==m);
			for(int k=1;k<=min(i,m);k++){
				mch[i-k+1]=mch[i-k+1]-match('?',t[k])+match(s[i],t[k]);
			}
		}
	} return 1;
}
int trs[44][MAXM+5],can[44][MAXM+5];
ll dp[44][MAXM+5][2],f[44][MAXM+5][2];
void add(ll &x,ll y){x=min(x+y,INF);}
void clear(){//remember to clear all!!
	memset(mch,0,sizeof(mch));memset(pos,0,sizeof(pos));pcnt=0;
	memset(ok,0,sizeof(ok));memset(ch,0,sizeof(ch));memset(fail,0,sizeof(fail));
	memset(trs,0,sizeof(trs));memset(can,0,sizeof(can));
	memset(dp,0,sizeof(dp));memset(f,0,sizeof(f));
}
void solve(){
	scanf("%d%d%s%s",&n,&qu,t+1,s+1);m=strlen(t+1);clear();
	for(int i=1;i<=n;i++) if(s[i]=='?') pos[++pcnt]=i;
	build();
	if(!calc_fst()){
		while(qu--) scanf("%*lld"),puts("-1");
		return;
	} int sum=0,pre=0,flg=0;
	for(int i=1;i<=n;i++) if(s[i]!='?')
		sum=(sum+1ll*(s[i]-'0')*pw[n-i])%MOD;
	if(pcnt==0){
		while(qu--){
			ll k;scanf("%lld",&k);
			printf("%d\n",(k==1)?sum:-1);
		} return;
	}
	for(int i=1;i<=pos[max(pcnt-40,0)+1]-1;i++){
		pre=ch[pre][s[i]-'0'];
		flg|=(pre==m);
	}
	pos[pcnt+1]=n+1;
	for(int i=1;i<=min(pcnt,40);i++){
		for(int j=0;j<=m;j++){
			int tmp=j;can[i][j]=(j==m);
			for(int k=pos[pcnt-i+1]+1;k<pos[pcnt-i+2];k++){
				tmp=ch[tmp][s[k]-'0'];
				can[i][j]|=(tmp==m);
			} trs[i][j]=tmp;
		}
	}
	for(int i=0;i<=min(pcnt,40);i++){
	//倒數第 i 個問號之前位於 j,目前是否出現 t(k=0 表示出現,k=1 表示不出現),符合條件的串的個數 
		for(int j=0;j<=m;j++) for(int k=0;k<2;k++){
			memset(f,0,sizeof(f));f[i][j][k]=1;
			for(int l=i;l;l--) for(int o=0;o<=m;o++) for(int p=0;p<2;p++){
				if(f[l][o][p]){
					for(int d=0;d<10;d++){
						add(f[l-1][trs[l][ch[o][d]]][p|can[l][ch[o][d]]],f[l][o][p]);
					}
				}
			} for(int o=0;o<=m;o++) add(dp[i][j][k],f[0][o][1]);
		}
	}
	while(qu--){
		ll k;scanf("%lld",&k);int stp=min(pcnt,40);
		if(dp[stp][pre][flg]<k) puts("-1");
		else{
			int cur=pre,cur_has=flg,res=sum;
			for(int i=stp;i;i--){
				for(int j=0;j<10;j++){
					int nw=trs[i][ch[cur][j]],nw_has=cur_has|can[i][ch[cur][j]];
					if(k>dp[i-1][nw][nw_has]) k-=dp[i-1][nw][nw_has];
					else{
						cur=nw;cur_has=nw_has;
						res=(res+1ll*j*pw[n-pos[pcnt-i+1]])%MOD;
						break;
					}
				}
			} printf("%d\n",res);
		}
	}
}
int main(){
	freopen("shuawang.in","r",stdin);
	freopen("shuawang.out","w",stdout);
	for(int i=(pw[0]=1);i<=MAXN;i++) pw[i]=10ll*pw[i-1]%MOD;
	int qu;scanf("%d",&qu);while(qu--) solve();
	return 0;
}
/*
1
106 1
12312
??12?1?3??1234?31??1?12?3?1?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?5?
1000000000000000000
*/