BZOJ 3326[Scoi2013]数数 数位dp+矩阵乘法优化

Posted TS_Hugh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ 3326[Scoi2013]数数 数位dp+矩阵乘法优化相关的知识,希望对你有一定的参考价值。

挺好的数位dp……
先说一下我个人的做法:
经过观察,发现这题按照以往的思路从后往前递增,不怎么好推,然后我就大胆猜想,从前往后推,发现很好推啊,维护四个变量,从开始位置到现在有了i个数
f[i]:所有数的所有未包含最后一位的子串的和
s[i]:所有数的所有后缀子串的和
c[i]:所有数的所有后缀子串的个数
n[i]:所有数共有多少个
他们的转移依次是(k为进制数)
f[i]=f[i-1]*k+s[i-1]*k
s[i]=s[i-1]*k*k+c[i-1]*k*(k-1)/2+n[i-1]*k*(k-1)/2
c[i]=c[i-1]*k+n[i-1]*k
n[i]=n[i-1]*k
我们发现对于最高位低于上界的数,我们可以在确定最高位上是1~9之后用上面的转移一遍O(n)dp算出来.如果最高位等于上界的话,我们的转移不太一样,但是也只不过是把某些k改为了这一位的上届,而且如果本位未达到上届,往后转移还是老样子,然而每次都要从前往后走一遍,会T,不过,这很明显是个可以用矩阵乘法优化的dp,因为他的转移方式每次都一样,所以我们就可以加速了,然而这是4*4的矩阵再加上一个log,吃不消啊,但是我们可以预处理转移i(1<=i<=max(n,m))次的矩阵,这样就可以做到O(4^3*n)了,又因为这个矩阵是个上三角矩阵,所以我们加一些矩阵乘法时的优化就可以有有着一个10左右常数的O(n)的做法了,我们解决了这道题!!!
现在说一下别人的做法:
A掉之后,去网上看了看别人的题解,发现从后往前递增并不是不可以,而且根本就没有人从前往后推,更没有任何人的做法跟矩阵乘法有半点关系……
他们就是从后往前递增,推出来一个关于k的次幂的式子,通过预处理k的次幂,加上对于上界的处理来递推……
他们的做法基本上都是O(n)的,但是跑得和我差不多……

#include <cstdio>
#include <cstring>
#include <algorithm>
char xB[(1<<15)+10],*xS,*xT;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
template <typename _t>
inline void read(_t &x){
  register char ch=gtc;bool ud=false;
  for(x=0;ch<\'0\'||ch>\'9\';ch=gtc)if(ch==\'-\')ud=true;
  for(;ch>=\'0\'&&ch<=\'9\';x=(x<<1)+(x<<3)+ch-\'0\',ch=gtc);
  if(ud)x=-x;
}
typedef long long LL;
const int P=20130427;
const int N=100010;
int a[4][4],b[4],s[N][4][4],temp_a[4][4],temp_b[4],c[4],d[4];
inline void get(int x[][4],int y){
  memset(temp_a,0,sizeof(a));
  register int i,j,k;
  for(i=0;i<4;++i)
    for(j=0;j<4;++j)
      if(a[i][j])
        for(k=0;k<4;++k)
          if(x[j][k])
            temp_a[i][k]=(temp_a[i][k]+(LL)x[j][k]*a[i][j])%P;
  memcpy(s[y],temp_a,sizeof(s[y]));
}
inline void run(int x[][4]){
  memset(temp_b,0,sizeof(temp_b));
  register int i,j;
  for(i=0;i<4;++i)
    for(j=0;j<4;++j)
      if(x[i][j])
        temp_b[i]=(temp_b[i]+(LL)x[i][j]*d[j])%P;
  memcpy(c,temp_b,sizeof(c));
}
int bit,digit[N],k,n,m,len;
inline int calc(){
  int ans=0,i;
  d[3]=0,d[2]=(LL)k*(k-1)/2%P,d[1]=k-1,d[0]=k-1;
  for(i=1;i<bit;++i)
    run(s[i-1]),ans=(ans+c[3]+c[2])%P;
  memset(b,0,sizeof(b)),b[0]=1;
  for(i=bit;i>0;--i){
    d[0]=((LL)b[0]*(digit[i]-(i==bit))%P);
    d[1]=((LL)b[1]*(digit[i]-(i==bit))+d[0])%P;
    d[2]=((LL)k*b[2]%P*(digit[i]-(i==bit))+(LL)b[1]*((LL)digit[i]*(digit[i]-1)/2%P)+(LL)b[0]*((LL)digit[i]*(digit[i]-1)/2%P))%P;
    d[3]=((LL)b[3]*(digit[i]-(i==bit))+(LL)b[2]*(digit[i]-(i==bit)))%P;
    run(s[i-1]);
    ans=(ans+c[3]+c[2])%P;
    b[3]=(b[3]+b[2])%P;
    b[2]=((LL)k*b[2]+(LL)(b[1]+b[0])*digit[i])%P;
    ++b[1];
  }
  return (ans+b[3]+b[2])%P;
}
int main(){
  read(k);int i,j,ans=0;
  a[3][3]=k,a[3][2]=k;
  a[2][2]=(LL)k*k%P,a[2][1]=((LL)k*(k-1)/2)%P,a[2][0]=((LL)k*(k-1)/2)%P;
  a[1][1]=k,a[1][0]=k;
  a[0][0]=k;
  s[0][0][0]=s[0][1][1]=s[0][2][2]=s[0][3][3]=1;
  for(read(n),i=n;i>0;--i)read(digit[i]);
  read(m),len=std::max(n,m);
  for(i=1;i<=len;++i)get(s[i-1],i);
  if(n==1)ans=(ans-(LL)digit[1]*(digit[1]-1)/2%P+P)%P;
  else{
    for(--digit[1],i=1;i<=n;++i)
      if(digit[i]<0)digit[i]+=k,--digit[i+1];
      else break;
    while(digit[n]==0)--n;
    bit=n,ans=(ans-calc()+P)%P;
  }
  for(i=m;i>0;--i)read(digit[i]);
  if(m==1)ans=(ans+(LL)digit[1]*(digit[1]+1)/2%P)%P;
  else bit=m,ans=(ans+calc())%P;
  printf("%d\\n",ans);
  return 0;
}

 

以上是关于BZOJ 3326[Scoi2013]数数 数位dp+矩阵乘法优化的主要内容,如果未能解决你的问题,请参考以下文章

[bzoj1072][SCOI2007][排列perm] (状态压缩+数位dp+排列去重)

BZOJ.3530.[SDOI2014]数数(AC自动机 数位DP)

[bzoj1026][SCOI2009]windy数_数位dp

bzoj3530[Sdoi2014]数数 AC自动机+数位dp

数位DP BZOJ 1026 [SCOI2009]windy数

luoguP3281 [SCOI2013]数数