Gym-101161 G 2016-2017 ACM-ICPC Asia-Bangkok Regional Contest(矩阵快速幂优化DP)

本题特别鸣谢菊苣:Gene_INNOCENT

题目链接:https://codeforces.com/gym/101161

 

题目大意:

题意:
01串,不包含两个连续的1。要求给出满足下述三个条件的01串的个数。(答案模1e9+7)

  • 01串长度在 [L,R] 之间 (1\leq l\leq r\leq 10^{18}
  • 01串长度是K的倍数(1\leq k\leq 10^{9})
  • 不包含连续两个1的01串

 

注:题意转自菊苣博客:https://blog.csdn.net/qq_41552508/article/details/97161365

 

题目思路:

首先一个显而易见(并不)的DP,设dp[i][0]表示长度为i的,以0为结尾的01串数量,dp[i][1]表示长度为i的,以1为结尾的01串数量,以0结尾的话,很显然前面可以跟0和1,但如果以1结尾,那么由于不能出现连续1,所以只能通过结尾为0的转换而来,所以得到DP方程如下:dp[i][0]=dp[i-1][0]+dp[i-1][1],dp[i][1]=dp[i-1][0]

然后打个表就可以发现,dp[i][0]+dp[i][1]居然是一个斐波那契数列!

当然,也可以通过证明得到:

dp[i][0]+dp[i][1]=dp[i-1][0]+dp[i-1][1]+dp[i-1][0]=dp[i-1][0]+dp[i-1][1]+dp[i-2][0]+dp[i-2][1]

把这个结论用d数组表示,那么d[1]=2,d[2]=3,d[3]=5....

到现在不要高兴得太早,最难得地方还没有开始!准确的说这才是最简单的部分!

我们题目要求的根本不是这玩意儿!

我们要得到的是d[k]+d[2k]+d[3k]+....

给这个前缀和定义一个数组叫sum[i],其意义是小于等于i的k的倍数的d数组的和

听上去有点绕口,实际上他就是这么绕口

所以题目就是sum[r/k]-sum[(l-1)/k]

那么我们怎么求出sum呢?l和r可是1e18的鸭!

这时候就要用上牛B的矩阵快速幂

由于在递推过程中,只有到k的倍数才更新sum,所以在到达k的倍数之前,直接就让sum等于原来的值,这样的递推进行k-1次,然后最后一次再进行更新,这样的过程持续n次,我们就能得到小于等于nk长度的所有k的倍数的d数组的和!

所以就是

\begin{pmatrix} dp[k-1] & dp[k] & sum[k] \end{pmatrix}*(\begin{pmatrix} 0 &1 & 0\\ 1 & 1 &0\\ 0& 0 & 1 \end{pmatrix}^{k-1} *\begin{pmatrix} 0 & 1 &1 \\ 1 &1 & 1\\ 0 & 0& 1 \end{pmatrix})^{r/k}=\begin{pmatrix} dp[2*k-1] & dp[2*k] & sum[2*k] \end{pmatrix}

感觉这题挺有意思的,打call

以下是代码:

#include<bits/stdc++.h>

using namespace std;
#define ll long long
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define per(i,a,b) for(ll i=a;i>=b;i--)
#define ll long long
const ll MOD = 1e9+7;
ll n;
void mul(ll f[3],ll a[3][3]){
    ll c[3];
    memset(c,0,sizeof(c));
    rep(j,0,2){
        rep(k,0,2){
            c[j]=(c[j]+(ll)f[k]*a[k][j])%MOD;
        }
    }
    memcpy(f,c,sizeof(c));
}
void mul2(ll a[3][3],ll b[3][3]){
    ll c[3][3];
    memset(c,0,sizeof(c));
    rep(i,0,2){
        rep(j,0,2){
            rep(k,0,2){
                c[i][j]=(c[i][j]+(ll)a[i][k]*b[k][j])%MOD;
            }
        }
    }
    memcpy(a,c,sizeof(c));
}
void mulself(ll a[3][3]){
    ll c[3][3];
    memset(c,0,sizeof(c));
    rep(i,0,2){
        rep(j,0,2){
            rep(k,0,2){
                c[i][j]=(c[i][j]+(ll)a[i][k]*a[k][j])%MOD;
            }
        }
    }
    memcpy(a,c,sizeof(c));
}
int main()
{
    ll t;
    scanf("%I64d",&t);
    ll l,r,k;
    rep(_,1,t){
        scanf("%I64d%I64d%I64d",&l,&r,&k);
        ll p[3][3]={{1,0,0},{0,1,0},{0,0,1}};
        ll p2[3][3];
        ll a[3][3]={{0,1,0},{1,1,0},{0,0,1}};
        ll n=k-1;
        for(;n;n>>=1){
            if(n&1)mul2(p,a);
            mulself(a);
        }
        ll b[3][3]={{0,1,1},{1,1,1},{0,0,1}};
        mul2(p,b);
        ll f1[3]={1,1,0};
        ll f2[3]={1,1,0};
        n=r/k;
        memcpy(p2,p,sizeof(p));
        for(;n;n>>=1){
            if(n&1)mul(f1,p);
            mulself(p);
        }
        n=(l-1)/k;
        for(;n;n>>=1){
            if(n&1)mul(f2,p2);
            mulself(p2);
        }
        ll ans=(f1[2]-f2[2]+MOD)%MOD;
        printf("Case %I64d: %I64d\n",_,ans);
    }
    return 0;
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值