JZOJ5416. 【NOIP2017提高A组集训10.22】密码 DP

168 篇文章 0 订阅

Description

现在身为校庆志愿者的小C正在引导校友们到他们集合的教室。终于,忙了一段时间的他可以休息一会儿了。这时,旁边一位老校友的话吸引到了他。“我后来当了一名探险家,有一次,我来到了一个地方,在正前方有一扇门,旁边写着一行文字:’现在给你前m个字符串G,有一个拼接规律T,它是一个长度为m的一个排列,你要把现在已经得到的最后m个字符串按照T的顺序拼接起来,得到一个新的字符串,用这种方法,你可得到第n个字符串P,再给你另外一个字符串S,则S在P中出现的次数就是这个门的密码…’”听到这里,小C陷入了沉思:到底密码是多少呢?不过由于小C比较讨厌大数,他只想知道这个密码除以(10^9+7)的余数。你能帮帮他吗?

Input

第一行包含一个整数m,表示开始字符串的个数(排列长度)。
接下来行,每行一个字符串,表示前m个字符串G,保证字符串的长度均相同。
接下来一行,包含m个正整数,表示T。
接下来一行,包含一个整数q,表示数据组数。
对于每一组数据,包含一个字符串和一个整数,分别表示S及n。

Output

输出总共行,每一个询问对应一个答案。

Sample Input

3
ab
ac
ca
2 3 1
3
ac 2
b 1
a 5

Sample Output

​1
1
5

Data Constraint

对于20%的数据,m<=2 ,|G|<=6 ,n<=15 。
对于30%的数据, m<=5 ,|G|<=50 ,n<=400 。
对于50%的数据,m<=5 ,|G|<=50 ,n<=20000 。
对于80%的数据, m<=9 ,|G|<=100 ,n<=10^6。
对于100%的数据, m<=9 ,|G|<=200 ,n<=10^9,q<=7,|S|<=|G|。

很鬼畜的题目,比赛的时候只会50,然后还因为传递地址等各种乱七八糟的c++语法然后挂了。。
80分应该是比较好大的,100分比较复杂我就放弃了,所以这里只讲80分的方法。
注意到n<=10^6,那么我们可以线性dp。
首先我们知道,如果要把m个连起来,那么他们的贡献就是原来每一块的贡献和连接处的贡献,然后线性推出f[i]。
先把每一块的贡献预处理出来直接算进f里面,然后连接处的贡献的话就设suf[i][j],pre[i][j],pre[i,j]表示第i个字符串的前j个字符和s的后j个字符能否匹配,suf[i][j]表示第i的字符串的后j个字符串和s的前j个字符串能否匹配,然后在预处理一些东西:
预处理link[i,j]表示把第i个串接到第j个串前面会多出现多少次s
记录form[i,0/1]分别表示第i个串前面/后面是哪个字符串
然后直接转移即可。
具体的话就是
f[i]+=link[from[im1+p[j]][1]][from[im1+p[j+1]][0]]
很好理解啦。
ymw真的强QAQ。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=2e4+5;
const int mo=1e9+7;
int n,m;
int f[N],w[20],link[20][20];
int p[20],len,from[N][2];
bool suf[205][205],pre[205][205];
char ch[205],s[20][205];
inline int solve()
{
    int l=strlen(ch+1);
    fo(i,1,m)
    {
        w[i]=0;
        fo(j,1,len-l+1)
        {
            int flag=1;
            fo(k,1,l)
            if (ch[k]!=s[i][j+k-1])
            {
                flag=0;
                break;
            }
            w[i]+=flag;
        }
        fo(j,1,l)
        {
            int flag=1;
            pre[i][j]=suf[i][j]=0;
            fo(k,1,j)
            if (ch[l-j+k]!=s[i][k])
            {
                flag=0;
                break;
            }
            pre[i][j]=flag,flag=1;
            fo(k,1,j)
            if (ch[j-k+1]!=s[i][len-k+1])
            {
                flag=0;
                break;
            }
            suf[i][j]=flag;
        }
    }
    fo(i,1,m)
    fo(j,1,m)
    {
        link[i][j]=0;
        fo(k,1,len-1)
        if (suf[i][k]&&pre[j][l-k])link[i][j]++;
    }
    fo(i,1,m)f[i]=w[i],from[i][0]=from[i][1]=i;
    fo(i,m+1,n)
    {
        f[i]=0;
        int tmp=i-m-1;
        fo(j,1,m)f[i]+=f[i-j],f[i]%=mo;
        fo(j,1,m-1)
        f[i]+=link[from[tmp+p[j]][1]][from[tmp+p[j+1]][0]],
        f[i]%=mo;
        from[i][0]=from[tmp+p[1]][0];
        from[i][1]=from[tmp+p[m]][1];
    }
    if (f[n]==0)f[n]=21;
    return f[n];

}
int main()
{
    freopen("password.in","r",stdin);
    freopen("password.out","w",stdout);
    scanf("%d",&m);
    fo(i,1,m)scanf("%s",s[i]+1);
    len=strlen(s[1]+1);
    fo(i,1,m)scanf("%d",&p[i]);
    int q;
    scanf("%d",&q);
    while (q--)
    {
        scanf("%s%d",ch+1,&n);
        printf("%d\n",solve());
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值