20191105 csp-s模拟T3(dp+kmp自动机+离线+Trie树)

T3 耍望节(shuawang)

(WOJ4795)
【题目描述】
L L L在小 Q Q Q的邀请下来到了古老的瑶族村落,村中正好在庆祝耍望节.
小Q介绍, 耍望节是瑶族的传统节日, 节日时各村寨的人们要聚在一起祭祀祖先,庆祝丰收。年轻的姑娘和小伙子们还要跳长鼓舞,通过对歌向意中人表达爱慕之情。虽然小 L L L对儿女情长没什么兴趣, 但是瑶族神秘的歌曲还是吸引了他。瑶族的歌曲中包含了十种音符, 小 L L L把这些音符从低到高按 0 到 9 0到9 09编号。他发现,一首曲子总是由 n n n个音符组成。小 Q Q Q告诉他,这是因为他们的祖先留下了T卷残缺的乐谱,每卷乐谱上记录了一支有着 n n n个音符的优美曲子,可惜年代过于久远,其中的某些音符已经看不清了。今日瑶族人的乐曲,是创作者们将乐谱中缺失的音符一一填补而形成的,不过每支乐曲最开始的一个音符还是清晰可见的,由于瑶族人认为以 0 0 0开头的曲目不大吉利,所以第一个音符不会是 0 0 0。小 Q Q Q还告诉小 L L L,瑶族的乐曲中总会包含一个世代相传的基本曲调 m m m。也就是说, m m m中的所有音符会在乐曲中按顺序连续出现。小 L L L听着瑶族居民悦耳的歌声,心里想着的却是数理方面的问题,他发现, 如果把一支瑶族的曲子从头到尾写下来,那么他会得到一个 n n n位十进制数。他突然很好奇,在所有可能的十进制数中,第 k k k小的是多少呢?
L L L觉得这个问题太简单了,所以现在你需要帮他回答。小 L L L是个好奇心很旺盛的人,他可能会问你很多个这样的问题. 当然他也不希望你太辛苦,所以他只需要你求出答案对 1 0 9 + 7 10^9+7 109+7取模的结果就行了。
【输入格式】
从文件shuawang.in中读入数据。
第一行一个整数 T T T, 表示数据组数。
每组数据第一行两个整数 n n n q q q,表示乐谱的长度和小 L L L的问题数量。
接下来一行一个字符串 m m m,表示乐曲的基本曲调。
接下来一行一个字符串 S S S描述残缺的乐谱,若 S S S的第 i i i位为 ? ? 则表示乐谱的第 i i i个位置上的音符缺失了。
随后 Q Q Q行每行一个正整数 k k k,表示小 L L L询问你第 k k k小数的是多少。
【输出格式】
输出到文件shuawang.out中。
对于每组数据,输出 q q q行。每行一个整数表示第 k k k小的数对 1 0 9 + 7 10^9+ 7 109+7取模之后的值,如果满足条件的数不足 k k k个,输出 − 1 -1 1
【样例1输入】
1
5 3
11
2?1?1
1
2
10000000000
【样例1输出】
20111
21101
-1
【样例1解释】
乐曲中必须包含连续的两个 1 1 1,所以最小的数是 20111 20111 20111,次小的是 21101 21101 21101。显然不存在 10000000000 10000000000 10000000000支满足条件的乐曲。
【样例2~4】
见选手目录下的shuawang/shuawang2 ~ 4.in与shuawang/shuawang2 ~ 4.ans。
【数据规模与约定】
对于所有数据, T ≤ 5 , n ≤ 5 × 1 0 4 , ∣ m ∣ ≤ 20 , q ≤ 1 0 5 , k ≤ 1 0 18 T\le 5,n\le 5\times 10^4,|m|\le 20,q\le 10^5,k\le 10^{18} T5,n5×104,m20,q105,k1018

思路:
神仙博客
先做成 K M P 自 动 机 KMP自动机 KMP,从后向前 d p dp dp(类似于求出 T r i e 树 Trie树 Trie每个节点的 s i z e size size),再离线询问,深搜 T r i e 树 Trie树 Trie,求解答案。

K M P 自 动 机 KMP自动机 KMP
主要是 K M P KMP KMP(废话)
类似于 A C 自 动 机 AC自动机 AC(废话*2)
KMP
AC自动机
K M P KMP KMP n e x t next next数组的基础上,拓展出 f a i l fail fail数组( f a i l [ i ] [ j ] fail[i][j] fail[i][j]即为这一位为 i i i,匹配 i + 1 i+1 i+1,成功匹配到 j j j时的位置)

inline void prepare(){
 	for(re int i=1,j=0;i<m;i++){
		while(j&&a[i+1]!=a[j+1])    j=nex[j];
		if(a[i+1]==a[j+1])  j++;
		nex[i+1]=j;
	}
	for(re int i=0;i<=m;i++)
		for(re int j=0;j<10;j++){
			if(i==m)    fail[i][j]=m;
			else{
				LL p=i;
				while(p&&a[p+1]!=j) p=nex[p];
				fail[i][j]=(a[p+1]==j)?p+1:0;
			}
		}
}

d p dp dp
d p [ i ] [ j ] dp[i][j] dp[i][j]为长字符串匹配到 i i i,短字符串匹配到 j j j时,后面的合法方案数。
d p [ n ] [ m ] dp[n][m] dp[n][m] 1 1 1,倒着 d p dp dp即可。(即求 T i r e 树 Tire树 Tire上合法节点的 s i z e size size

inline void do_dp(){
	dp[n][m]=1;
	for(re int i=n-1;i>=0;i--)
		for(re int j=0;j<=m;j++)
			for(re int k=0;k<10;k++){
				if(!i&&!k)  continue;
				if(b[i+1]==k||b[i+1]==-1)
					dp[i][j]=min(dp[i][j]+dp[i+1][fail[j][k]],INF);
			}
	return;
}

离线求解:
T i r e 树 Tire树 Tire上深搜。发现当前子树上没有要求答案,累加 s i z e size size,进入下一棵子树。发现当前子树上需求解答案,进入子树求解。

inline void get_ans(LL pos_b,LL pos_a,LL num,LL val){
	if(pos>now) return;
	if(pos_b>n){
		while(qur[pos].id==num+1&&pos<=now){
			ans[qur[pos].ans]=val;
			pos++;
		}
		return;
	}
	for(re int i=0;i<10;i++)
		if(b[pos_b]==i||b[pos_b]==-1){
			if(dp[pos_b][fail[pos_a][i]]+num>=qur[pos].id)
				get_ans(pos_b+1,fail[pos_a][i],num,((val*10)%mod+i)%mod);
			if(pos>now||dp[pos_b][fail[pos_a][i]]==INF) return;
			num+=dp[pos_b][fail[pos_a][i]];
		}
	return;
}

代码:

#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define LL long long
#define re register

inline LL in{
	LL s=0,f=1;char x;
	for(x=getchar();!isdigit(x);x=getchar())    if(x=='-')  f=-1;
	for( ;isdigit(x);x=getchar())   s=(s<<1)+(s<<3)+(x&15);
	return s*f;
}

const int A=1e6+5;
const LL INF=1e18+5;
const int mod=1e9+7;
int t;
int n,q;
char ch[A];
LL m,a[50];//短串
LL nex[50],fail[50][50];
LL b[A];//长串
LL dp[A][50];
struct Qurey{
	LL id,ans;
	inline friend bool operator < (const Qurey &x,const Qurey &y){
		return x.id<y.id;
	}
}qur[A];//询问
LL now,pos;
LL ans[A];

inline void prepare(){
 	for(re int i=1,j=0;i<m;i++){
		while(j&&a[i+1]!=a[j+1])    j=nex[j];
		if(a[i+1]==a[j+1])  j++;
		nex[i+1]=j;
	}
	for(re int i=0;i<=m;i++)
		for(re int j=0;j<10;j++){
			if(i==m)    fail[i][j]=m;
			else{
				LL p=i;
				while(p&&a[p+1]!=j) p=nex[p];
				fail[i][j]=(a[p+1]==j)?p+1:0;
			}
		}
}

inline void do_dp(){
	dp[n][m]=1;
	for(re int i=n-1;i>=0;i--)
		for(re int j=0;j<=m;j++)
			for(re int k=0;k<10;k++){
				if(!i&&!k)  continue;
				if(b[i+1]==k||b[i+1]==-1)
					dp[i][j]=min(dp[i][j]+dp[i+1][fail[j][k]],INF);
			}
	return;
}

inline void get_ans(LL pos_b,LL pos_a,LL num,LL val){
	if(pos>now) return;
	if(pos_b>n){
		while(qur[pos].id==num+1&&pos<=now){
			ans[qur[pos].ans]=val;
			pos++;
		}
		return;
	}
	for(re int i=0;i<10;i++)
		if(b[pos_b]==i||b[pos_b]==-1){
			if(dp[pos_b][fail[pos_a][i]]+num>=qur[pos].id)
				get_ans(pos_b+1,fail[pos_a][i],num,((val*10)%mod+i)%mod);
			if(pos>now||dp[pos_b][fail[pos_a][i]]==INF) return;
			num+=dp[pos_b][fail[pos_a][i]];
		}
	return;
}

inline void clean(){
	memset(nex,0,sizeof(nex));
	memset(fail,0,sizeof(fail));
	memset(dp,0,sizeof(dp));
}

signed main(){
	t=in;
	while(t--){
		clean();
		n=in,q=in;
  		scanf("%s",ch+1);
		m=strlen(ch+1);
		for(re int i=1;i<=m;i++)
			a[i]=ch[i]-'0';
		prepare();//kmp自动机
		scanf("%s",ch+1);
		for(re int i=1;i<=n;i++)
			b[i]=(isdigit(ch[i]))?ch[i]-'0':-1;
		do_dp();//dp
		for(re int i=1;i<=q;i++){
			qur[i].id=in;
			qur[i].ans=i;
		}
		sort(qur+1,qur+1+q);
		now=q,pos=1;
		while(now&&qur[now].id>dp[0][0]){
			ans[qur[now].ans]=-1;
			now--;
		}
		get_ans(1,0,0,0);//深搜求解
		for(re int i=1;i<=q;i++)
			printf("%lld\n",ans[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值