【BZOJ4002】【JLOI2015】有意义的字符串(数学,矩阵快速幂)

看来我数学还是太弱了,这道题也没有想到。
我们发现 (b+d2)n ( b + d 2 ) n 是个无理数,而且这个数非常大,所以无法直接计算。我们又发现 (bd2)n ( b − d 2 ) n 的值虽然是个无理数,但是他的值向下取整之后非常好计算。然后我们又发现, (b+d2)n+(bd2)n ( b + d 2 ) n + ( b − d 2 ) n 是个有理数,又因为 b mod 2=1,d mod 4=1 b   m o d   2 = 1 , d   m o d   4 = 1 ,所以不难证明这个数是一个整数。
所以,我们可以将问题转变为求 [(b+d2)n+(bd2)n](bd2)n ⌊ [ ( b + d 2 ) n + ( b − d 2 ) n ] − ( b − d 2 ) n ⌋
然后我们就可以把问题拆成两部分。
首先我们考虑第一部分。我们可以构造一个一元二次方程: x2b+b2d4=0 x 2 − b + b 2 − d 4 = 0 ,那么它的解 x1=b+d2,x2=bd2 x 1 = b + d 2 , x 2 = b − d 2
那么第一部分就等于 xn1+xn2 x 1 n + x 2 n
然后我们把这个式子变形一下就可以得到: xn1+xn2=(x1+x2)(xn11+xn12)x1x2(xn21+xn22) x 1 n + x 2 n = ( x 1 + x 2 ) ( x 1 n − 1 + x 2 n − 1 ) − x 1 x 2 ( x 1 n − 2 + x 2 n − 2 )
然后令 F(n)=xn1+xn2 F ( n ) = x 1 n + x 2 n
可以得到 F(n)=(x1+x2)F(n1)x1x2F(n2) F ( n ) = ( x 1 + x 2 ) F ( n − 1 ) − x 1 x 2 F ( n − 2 )
然后我们构造的一元二次方程就派上用场了!
根据韦达定理,对于一个一元二次方程 ax2+bx+c=0 a x 2 + b x + c = 0 的两个解 x1,2 x 1 , 2 满足: x1+x2=ba,x1x2=ca x 1 + x 2 = − b a , x 1 x 2 = c a
所以 F(n)=bF(n1)+db22F(n2) F ( n ) = b F ( n − 1 ) + d − b 2 2 F ( n − 2 )
其中 F(0)=2,F(1)=b F ( 0 ) = 2 , F ( 1 ) = b
推到这里就是非常经典的的矩乘快速递推啦!
对于第二部分,
b2d<(b+1)2 ∵ b 2 ≤ d < ( b + 1 ) 2
b,dN ∵ b , d ∈ N
bd<b+1 ∴ b ≤ d < b + 1
bd2(1,0] ∴ b − d 2 ∈ ( − 1 , 0 ]
不难发现,当且仅当 n n 为奇数且d!=b时,这一部分对答案有 1 − 1 的贡献。所以我们判一下是否减 1 1 就行了。
有一个坑点,这个模数非常大,加起来可能会爆long long,所以我们加的时候要判一下它们的和是否小于 0 0
注意两个数相乘会爆long long,所以要用快速乘。
快速乘其实主要就是利用 long double l o n g   d o u b l e 的较高的精度,再利用我们小学学的一个东西(被除数减除数乘商等于余数),就可以在 O(1) O ( 1 ) 的时间内完成较大的数的乘法并取模。
如果有错在评论区吼一声哦!
代码:

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=7528443412579576937ll;
ll b,d,n;
ll Add(ll x,ll y){
    return x+y>=mod||x+y<0?x+y-mod:x+y;
}
ll Minus(ll x,ll y){
    return x-y<0?x-y+mod:x-y;
}
ll Mul(ll x,ll y){
    ll d=(ll)(x*(long double)y/mod+0.5);
    return Minus(x*y,d*mod);
}
struct Matrix{
    ll a[2][2];
    ll *operator[](int b){
        return a[b];
    }
    friend Matrix operator*(Matrix A,Matrix B){
        Matrix ret;
        for(int i=0;i<2;i++)
            for(int j=0;j<2;j++){
                ret[i][j]=0;
                for(int k=0;k<2;k++)
                    ret[i][j]=Add(ret[i][j],Mul(A[i][k],B[k][j]));
            }
        return ret;
    }
    Matrix operator*=(Matrix B){
        *this=*this*B;
        return *this;
    }
}A,B;
Matrix qpow(Matrix x,ll n){
    Matrix ret;
    ret[0][0]=ret[1][1]=1,ret[0][1]=ret[1][0]=0;
    while(n){
        if(n&1ll)
            ret*=x;
        x*=x;
        n>>=1ll;
    }
    return ret;
}
int main(){
    scanf("%lld%lld%lld",&b,&d,&n);
    if(!n){
        puts("1");
        return 0;
    }
    A[0][0]=b;
    A[0][1]=2;
    B[0][0]=b;
    B[0][1]=1;
    B[1][0]=(d-b*b)/4;
    A[1][0]=A[1][1]=B[1][1]=0;
    A*=qpow(B,n-1);
    printf("%lld",Minus(A[0][0],!(n&1ll)&&(d!=b*b)));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值