51nod1668 非010串(矩阵快速幂)

1668 非010串
基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注
如果一个01字符串满足不存在010这样的子串,那么称它为非010串。
求长度为n的非010串的个数。(对1e9+7取模)
Input
一个数n,表示长度。(n<1e15)
Output
长度为n的非010串的个数。(对1e9+7取模)
Input示例
3
Output示例
7

解释:
000
001
011
100
101
110
111

见到这题时,是去年的算法马拉松,当时自己不会,问学长~~也不会, 讨论了一顿,还是不会。。。。今年为了准备邀请赛重新开刷了51nod,弱鸡表示很无力。。。。。突然想起这题,就跑去看题了,发现切入的时候思路还是挺清晰的。。。。。
一开始看错题目,以为求非01串,觉得这题莫名简单啊。。。。。推了个递推式
f[n]=2f[n-1]-f[n-3],
然后看数据是1e15,马上开心的写了一个矩快。。。。
一交,全WA,
一看题发现自己看错了,,,两种情况的递推公式还是不一样的,,,,

思路:要计算长度为n的非010串的数目???看到长度为n,我想应该dp吧??
为什么想到dp?
xxx010 一定是从 xxx01转移得到的,
xxx01一定是从xxx0转移得到的,
xxx0,一定是由xxx转移得到得
确立了以上的关系,就能得出状态转移式,然后求解

把非010串定义为合法串,即其他的是非法串
那就看看Fn-1(长度为n-1时,合法串的数量)和Fn(长度为n时,合法串的数量)有什么关系,
约定:
Fn:长度为n的合法串数量
Sn:长度为n,末尾两位是01的合法串数量
An:长度为n,末位是0的合法串数量
1。首先发现Fn的上界应该是2*Fn-1:每个长度为n-1的合法串, 都能变成2个长度为n的字符串(可能是合法串也可能是非法串)

2。显然发现1中存在一条不确定关系,到底如何确定,n-1的合法串转化的是长度n的合法串还是非法串?
观察一下就很容易发现,“010”一定是由“01”+“0”转化而来的,这样一来1中不确定的关系就确定了:
长度为n的合法串的数量=长度为n-1时的合法串的数量*2-长度为n-1后两位是01的合法串 即:Fn=2*Fn-1 - Sn-1

那么Sn?
长度为n后两位是01的合法串 = 长度为n-1末位是0的合法串
Sn=An-1
那么:An?
长度为n末位是0的合法串=长度为n-1合法串-长度为n-1末尾是01合法串
An=Fn-1-Sn-1
为什么要减去Sn-1?因为01+0=010,这是非法的字符串,要去掉

那这样所有的递推关系就明了了:
Fn:长度为n的合法串数量
Sn:长度为n,末尾两位是01的合法串数量
An:长度为n,末位是0的合法串数量

Fn=2*Fn-1 - Sn-1
Sn=An-1
An=Fn-1-Sn-1

由上面可知,只要有F1,S1,A1,就一定能递推得到Fn,Sn,An
矩阵快速幂:

   2 -1 0       Fn-1    Fn
   0 0  1   *   Sn-1  = Sn
   1 -1 0       An-1    An

代码:

#include <iostream>
using namespace std;
typedef long long ll;
struct mt{
ll m[10][10];
ll h,l;
};
ll md=1e9+7;
mt mul(mt a,mt b){
mt res;
res.h=a.h,res.l=b.l;
for(int i=0;i<a.h;i++){
    for(int j=0;j<b.l;j++){
        res.m[i][j]=0;
    }
}
for(int i=0;i<a.h;i++){
    for(int j=0;j<b.l;j++){
        for(int x=0;x<a.l;x++){
            res.m[i][j]= (res.m[i][j]%md+(a.m[i][x]%md)*(b.m[x][j]%md)%md+md)%md;
        }
    }
}



return res;}

mt quick_mod(mt a,mt b,ll n){
mt res=b;
while(n){
    if(n&1){
        res=mul(a,res);
    }
    a=mul(a,a);
    n=n>>1;

}
return res;

}

int main()
{
   ll n;
   cin>>n;
   if(n==0){
    cout<<"0"<<endl;
    return 0;
   }
    if(n==1){
    cout<<"2"<<endl;
    return 0;
   }

  n=n-1;
  mt a,b;
  a.h=a.l=3;
  b.h=3;
  b.l=1;
  a.m[0][0]=2, a.m[0][1]=0,a.m[0][2]=-1,
  a.m[1][0]=1,a.m[1][1]=0;a.m[1][2]=-1;
  a.m[2][0]=0,a.m[2][1]=1;a.m[2][2]=0;

  b.m[0][0]=2,b.m[1][0]=1; b.m[2][0]=0;
  mt res=quick_mod(a,b,n);
  cout<<res.m[0][0]<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值