洛谷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]\)转化成,初始矩阵\(\times a^i\)的形式。

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

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

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

\[f[i+j]=a^{i+j} = a^i \times 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\times 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;
}

转载于:https://www.cnblogs.com/yimmortal/p/10172074.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解题思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算逆序对数; 4. 输出逆序对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算逆序对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算逆序对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值