【NOI2012】随机数生成器

Description

给你一个式子

Xn+1=(aXn+c)modm

求第n项

Solution

用什么

发现n十分的大 1018 ,又只给1000ms,只能用 O(logn) 的方法
有什么可以做呢?
很明显是矩阵乘法!!!

构造一个转移矩阵。

先要弄初始矩阵。
先设初始矩阵有一位 Xn ,首先需要乘,并不用多开一位。要从 Xn1 推过来,多开一位 Xn1 。然后还要加c,再多开一位c。
那么初始矩阵有3位,[ Xn1 ][ Xn ][ c ]
那么很明显要从 Xn 推到 Xn+1 ,那么很明显转移矩阵
{{0,0,0},
{1,a,0},
{0,1,1}
}

发现普通的乘法会爆long long,怎么办?

可以用快速乘,类似快速幂,速度比快速幂多一个log
比如说是a*b,我们设f(i)=a*i
那么把b拆一下,f(b)=f(b/2) * 2+a *(b mod 2)

ll qsc(ll x,ll y){
    ll z=0;
    if(y==0)return z;
    z=qsc(x,y/2);
    z=z*2%m;
    if(y%2==1)z=(z+x)%m;
    return z;
}

其实还可以用黑科技
把这些数强制转成double再转回来

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define ll long long
using namespace std;
ll i,j,k,l,t,n,m,ans,a,b,c,g,x0,x1;
ll qsc(ll x,ll y){
    ll z=0;
    if(y==0)return z;
    z=qsc(x,y/2);
    z=z*2%m;
    if(y%2==1)z=(z+x)%m;
    return z;
}
struct node{
    ll ju[3][3];
    node friend operator *(node a,node b){
        node c;
        memset(c.ju,0,sizeof(c.ju));
        fo(i,0,2){
            fo(j,0,2){
                fo(k,0,2){
                    c.ju[i][j]=(c.ju[i][j]+qsc(a.ju[i][k],b.ju[k][j]))%m;
                }
            }
        }
        return c;
    }
}f,ber;
void qsm(node x,ll y){
    while(y!=0){
        if(y&1==1)f=f*x;
        x=x*x;
        y=y/2;
    }
}
int main(){
    scanf("%lld%lld%lld%lld%lld%lld",&m,&a,&c,&x0,&n,&g);
    ber.ju[0][0]=0;ber.ju[0][1]=0;ber.ju[0][2]=0;
    ber.ju[1][0]=1;ber.ju[1][1]=a;ber.ju[1][2]=0;
    ber.ju[2][0]=0;ber.ju[2][1]=1;ber.ju[2][2]=1;
    x1=(qsc(a,x0)+c)%m;
    f.ju[0][0]=x0,f.ju[0][1]=x1,f.ju[0][2]=c;
    qsm(ber,n-1);
    printf("%lld",f.ju[0][1]%g);
}

你可能感兴趣的:(快速幂,随机数生成器,矩阵乘法,NOI,快速乘)