zoukankan      html  css  js  c++  java
  • 洛谷3176 [HAOI2015]数字串拆分 (矩阵乘法+dp)

    qwq真的是一道好题qwq自己做基本是必不可能做出来的。

    首先,如果这个题目只是求一个(f)数组的话,那就是一道裸题。
    首先,根据样例 根据题目描述,我们能发现其实同样数字的不同排列,也是属于不同的方案的,那统计起来其实方便很多。

    首先我们发现,对于(i)这个数,他可以拆出来([1,m])任何一个数,接在对应的(f[i-1]到f[i-m])

    也就是说(f[i]=f[i-1]+f[i-2]+f[i-3]....f[i-m])

    qwq那我们可以构造出转移矩阵
    (m=3)为栗子

    0 0 1
    1 0 1
    0 1 1
    

    我们假设这个转移矩阵是(a)

    那我们就可以直接将每一个(f[i])转化成,初始矩阵( imes a^i)的形式。

    qwq但这个离我们求出来(g)数组还差好远。

    由于(g)数组涉及(f)数组的拆分形式。

    那我们不妨观察一下(f[i+j])等于多少。

    [f[i+j]=a^{i+j} = a^i imes a^j = f[i]*f[j] ]

    那么我们就可以直接用矩阵乘法的形式来表示拼接了。

    那g数组的转移式子,也就比较好求了

    [g[i]=sum_{j=0}^{i-1}g[j]*d[j+1][i] ]

    其中(d[j+1][i])表示([j+1,i])这些数从左到右排起来,组成的数的(f)的对应矩阵是多少。

    这里转移式子的意义是,我们对于当前位,考虑枚举所有他的后缀,和前面任意的(f[i])的值乘起来,都是一个合法的方案(原理根据上面对(f[i+j])的讨论)。
    之所以能直接用(g)数组来乘转移矩阵而不是一个一个分别乘。

    是因为同大小的矩阵具有乘法分配律!

    所以(g)也就可以直接和对应矩阵乘起来了

    那么最后的(ans),就应该是初始矩阵
    还是以(m=3)为例

    0 0 1
    0 0 0
    0 0 0
    

    乘上(g[n])之后第一行最后一个元素的值,也就相当于(g[n])的第m行m列的那个元素。

    现在整个问题的瓶颈到了怎么求(d)数组,由于数值太大,所以我们没有办法直接快速乘。
    qwq
    现在考虑递推

    我们令(b[i][j])表示(i imes 10^j)的对应的f的转移矩阵是多少。

    比较容易发现这个数组还是很好递推的。

    每次(b[i][j]=qsm(b[i][j-1],10))

    那知道这个数组,其实(d)数组也就不难推了

    我们首先令(d[i][i]=b[s[i]-'0'][0])

    那么(d[j][i]=d[j+1][i]*b[s[j]-'0'][i-j])

    那么到这里这个问题也就基本解决了。

    感觉细节真的是很多。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #include<set>
    #define mk make_pair
    #define ll long long
    #define int long long
    using namespace std;
    inline int read()
    {
      int x=0,f=1;char ch=getchar();
      while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
      while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
      return x*f;
    }
    const int maxn = 7;
    const int mod = 998244353;
    struct Ju{
      int x,y;
      int a[maxn][maxn];
      Ju operator * (Ju b)
      {
      	 Ju ans;
      	 memset(ans.a,0,sizeof(ans.a));
      	 ans.x=x;
      	 ans.y=b.y;
      	 for (int i=1;i<=ans.x;i++)
      	   for (int j=1;j<=ans.y;j++)
             for (int k=1;k<=y;k++)
               ans.a[i][j]=(ans.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
         return ans; 
      }
      Ju operator +(Ju b)
      {
      	 Ju ans;
      	 memcpy(ans.a,a,sizeof(ans.a));
      	 ans.x=x;
      	 ans.y=y;
      	 for (int i=1;i<=x;i++)
      	   for (int j=1;j<=y;j++)
      	     ans.a[i][j]=(ans.a[i][j]+b.a[i][j])%mod;
         return ans; 
      }
    };
    Ju qsm(Ju i,int j)
    {
        Ju ans;
        memset(ans.a,0,sizeof(ans.a));
        ans.x=i.x;
        ans.y=i.y;
        for (int p=1;p<=i.x;p++) ans.a[p][p]=1;
        while (j)
        {
            if (j&1) ans=ans*i;
            i=i*i;
            j>>=1;
        }
        return ans;
    }
    char s[2020];
    int n,m;
    Ju a[510][510]; //a[i][j]表示,区间[i,j]的数的矩阵是多少
    Ju ymh[510][510]; //ymh[i][j]表示,i*10^j的矩阵是多少
    Ju lyf; 
    Ju g[510];//大小相同的矩阵乘法具有分配律 
    void init() //初始化矩阵的行和列 
    {
        for (int i=0;i<=9;i++)
          for (int j=0;j<=n;j++)
          { 
            ymh[i][j].x=ymh[i][j].y=m;
          }
        for (int i=0;i<=n;i++)
          for (int j=0;j<=n;j++)
            {
              a[i][j].x=a[i][j].y=m;
            }
        for (int i=0;i<=n;i++) g[i].x=g[i].y=m;
    }
    void print(Ju a)
    {
        cout<<"*******"<<endl;
        cout<<a.x<<" "<<a.y<<endl;
        for (int i=1;i<=a.x;i++)
        {
            for (int j=1;j<=a.y;j++)
              cout<<a.a[i][j]<<" ";
            cout<<endl;
        }
    }
    signed main()
    {
      init();
      scanf("%s",s+1);
      m=read();
      n=strlen(s+1);
      init();	
      ymh[0][0].x=m;
      ymh[0][0].y=m;
      for (int i=1;i<=m;i++) ymh[0][0].a[i][i]=1; //设置初始矩阵 
      lyf.x=m;
      lyf.y=m;
      for (int i=1;i<m;i++) lyf.a[i+1][i]=1;
      for (int i=1;i<=m;i++) lyf.a[i][m]=1; //设置转移矩阵 
      
      for (int i=1;i<=9;i++)
        ymh[i][0]=ymh[i-1][0]*lyf; //先递推出来所有i*10^0的答案 
      for (int i=0;i<=9;i++)
        for (int j=1;j<=n;j++)
          ymh[i][j]=qsm(ymh[i][j-1],10); //预处理出来所有的值 
     // print(ymh[1][0]);
     // print(ymh[0][0]);
      for (int i=n;i>=1;i--)
      {
      	 a[i][i]=ymh[s[i]-'0'][0];
      	 for (int j=i-1;j>=1;j--)
      	 {
      	 	a[j][i]=ymh[s[j]-'0'][i-j]*a[j+1][i];
         }
      } //预处理a数组 
      //print(a[1][1]);
      g[0]=ymh[0][0];
      for (int i=1;i<=n;i++)
      {
      	 for (int j=i-1;j>=0;j--)
      	   g[i]=g[i]+(g[j]*a[j+1][i]); //计算g,因为g是个sigma的数组,而且同大小矩阵乘法具有分配律,所以这句话表示,当前[j+1,i]这个串可以和之前任意一种组合,组合成一个串。 
      }
      int ans=0; 
      //print(g[n]);
      //for (int i=1;i<=m;i++) ans=(ans+g[n].a[1][i])%mod;
      //cout<<ans<<endl;
      cout<<g[n].a[m][m]<<endl;
      return 0;
    }
    
    
  • 相关阅读:
    三分法
    string常用函数的整理
    一句话 讲解 kmp的 next 数组 看不懂的 直接来掐死我吧
    http://www.codeforces.com/contest/703/problem/D D. Mishka and Interesting sum (莫队的TLE)
    Codeforces Round #365 (Div. 2) C
    数论
    默慈金数
    转载:HTTP 请求头中的 X-Forwarded-For
    Glusterfs volume 的三种挂载方式
    GlusterFS 配置及使用
  • 原文地址:https://www.cnblogs.com/yimmortal/p/10172074.html
Copyright © 2011-2022 走看看