T3 耍望节(shuawang)
(WOJ4795)
【题目描述】
小
L
L
L在小
Q
Q
Q的邀请下来到了古老的瑶族村落,村中正好在庆祝耍望节.
小Q介绍, 耍望节是瑶族的传统节日, 节日时各村寨的人们要聚在一起祭祀祖先,庆祝丰收。年轻的姑娘和小伙子们还要跳长鼓舞,通过对歌向意中人表达爱慕之情。虽然小
L
L
L对儿女情长没什么兴趣, 但是瑶族神秘的歌曲还是吸引了他。瑶族的歌曲中包含了十种音符, 小
L
L
L把这些音符从低到高按
0
到
9
0到9
0到9编号。他发现,一首曲子总是由
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}
T≤5,n≤5×104,∣m∣≤20,q≤105,k≤1018。
思路:
神仙博客
先做成
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;
}