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
*/
posted @ 2021-10-21 14:31  tzc_wk  阅读(59)  评论(0)    收藏  举报