【JZOJ 5416】【NOIP2017提高A组集训10.22】密码

Description

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

Solution

首先,对于原串内产生的贡献(就是不计算因合并两串而新产生的贡献)容易计算,直接用一个矩阵乘法即可,

考虑因合并两串而新产生的贡献怎么算,
合并两串的贡献只与开头结尾是哪个串有关,其他的可以递推转移,
对于第i个串的开头,它只与第(i-m+b[1]-1)个串的开头有关,
所以开头肯定是有循环的,且循环节最长为n,
结尾也同理,所以它们两合在一起的循环节最长为m*(m-1),
那么就找出这个循环节,再用矩阵快速幂即可,

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define min(q,w) ((q)>(w)?(w):(q))
#define max(q,w) ((q)<(w)?(w):(q))
using namespace std;
typedef long long LL;
const int N=205,mo=1e9+7;
int m,n,m1,m2,n1,n2;
LL ans;
int a[10][205];
int b[10],c[N],cx[N];
LL f[N];
LL jz[N][N];
LL jz1[N][N];
int sm[N][N];
map<int,int>Hx;
LL d[N*N*2][4];
void chenj(int n)
{
    fo(i,1,n)fo(k,1,n)fo(j,1,n)jz1[i][j]=(jz1[i][j]+jz[i][k]*jz[k][j])%mo;
    fo(i,1,n)fo(j,1,n)jz[i][j]=jz1[i][j],jz1[i][j]=0;
}
void chenans(int n)
{
    fo(i,1,n)jz1[0][i]=0;
    fo(j,1,n)fo(i,1,n)jz1[0][i]=(jz1[0][i]+jz[j][i]*f[j])%mo;
    fo(i,1,n)f[i]=jz1[0][i],jz1[0][i]=0;
}
void JZksm(int w,int n)
{
    for(;w;w>>=1,chenj(n))if(w&1)chenans(n);
}
int HX(int l)
{
    int ans=0;
    fo(i,l,l+n-1)ans=ans*n*n+d[i][0]*n+d[i][1];
    return ans;
}
int main()
{
    freopen("password.in","r",stdin);
    freopen("password.out","w",stdout);
    int q,w,e;char ch;
    scanf("%d",&n);
    fo(i,1,n)
    {
        for(ch=' ';ch<'a'||ch>'z';ch=getchar());
        for(q=1;ch<='z'&&ch>='a';ch=getchar(),q++)a[i][q]=ch-96;
    }
    m=q-1;
    fo(i,1,n)scanf("%d",&b[i]);
    scanf("%d",&m1);
    fo(i,1,m1)
    {
        memset(jz,0,sizeof(jz));
        memset(d,0,sizeof(d));
        for(ch=' ';ch<'a'||ch>'z';ch=getchar());
        for(m2=1;ch<='z'&&ch>='a';ch=getchar(),m2++)c[m2]=ch-96;
        m2--;
        scanf("%d",&n1);
        q=0;
        fo(i,2,m2)
        {
            while(q&&c[q+1]!=c[i])q=cx[q];
            if(c[q+1]==c[i])q++;
            cx[i]=q;
        }
        ans=0;
        fo(i,1,n)
        {
            q=0;
            int t=0;
            fo(j,1,m)
            {
                while(q&&c[q+1]!=a[i][j])q=cx[q];
                if(c[q+1]==a[i][j])q++;
                if(q==m2)q=cx[q],t++;
            }
            f[i]=t;
        }
        if(n1>n)
        {
            fo(i,1,n-1)jz[i+1][i]=1;
            fo(i,1,n)jz[i][n]=1;
            JZksm(n1-n,n);
            ans=f[n];
        }else ans=f[n1];
        fo(i,1,n)fo(j,1,n)
        {
            q=0;
            fo(k,m-m2+2,m)
            {
                while(q&&c[q+1]!=a[i][k])q=cx[q];
                if(c[q+1]==a[i][k])q++;
            }
            sm[i][j]=0;
            fo(k,1,m2-1)
            {
                while(q&&c[q+1]!=a[j][k])q=cx[q];
                if(c[q+1]==a[j][k])q++;
                if(q==m2)q=cx[q],sm[i][j]++;
            }
        }
        Hx.clear();
        fo(i,1,n)d[i][0]=d[i][1]=i,d[i][2]=0;
        int t=HX(1);
        Hx[t]=1;
        bool BK=0;
        fo(i,n+1,1e9)
        {
            if(i>n1){ans=(ans+d[i-1][2])%mo;BK=1;break;}
            d[i][0]=d[i-n+b[1]-1][0];
            d[i][1]=d[i-n+b[n]-1][1];
            d[i][2]=d[i-n+b[1]-1][2];
            fo(j,2,n)
            {
                d[i][2]+=d[i-n-1+b[j]][2];
                if(d[i][2]>=mo)d[i][2]-=mo;
                d[i][3]+=sm[d[i-n-1+b[j-1]][1]][d[i-n-1+b[j]][0]];
                if(d[i][3]>=mo)d[i][3]-=mo;
            }
            d[i][2]+=d[i][3];
            if(d[i][2]>=mo)d[i][2]-=mo;
            t=HX(i-n+1);
            if(Hx[t])
            {
                t=Hx[t];
                if(t+2*n-1>=i)continue;
                ans=(ans+d[t-1][2])%mo;
                n1-=t-1;
                n2=i-t+1-n+1;
                fo(j,1,n2-1)f[j]=d[t+j-1][2];
                f[n2]=1;
                memset(jz,0,sizeof(jz));
                int I=i-n;
                fo(j,1,n2-1)
                {
                    I++;
                    if(j>n)
                    {
                        d[I][0]=d[I-n-1+b[1]][0];
                        d[I][1]=d[I-n-1+b[n]][1];
                        fo(k,2,n)
                        {
                            d[I][3]+=sm[d[I-n-1+b[k-1]][1]][d[I-n-1+b[k]][0]];
                            if(d[I][3]>=mo)d[I][3]-=mo;
                        }
                    }
                    fo(k,n2-n+j-1,n2-1)jz[k][j]++;
                    fo(k,max(1,j-n),j-1)fo(l,1,n2)
                    {
                        jz[l][j]+=jz[l][k];
                        if(jz[l][j]>=mo)jz[l][j]-=mo;
                    }
                    jz[n2][j]+=d[I][3];
                    if(jz[n2][j]>=mo)jz[n2][j]-=mo;
                }
                jz[n2][n2]=1;
                break;
            }else Hx[t]=i-n+1;
        }
        if(BK){printf("%lld\n",ans);continue;}
        JZksm((n1-1)/(n2-1),n2);
        if(n1%(n2-1)==0)ans=(ans+f[n2-1])%mo;
        else ans=(ans+f[n1%(n2-1)])%mo;
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值