【NOI2012】随机数生成器

Description

栋栋最近迷上了随机算法,而随机数生成是随机算法的基础。栋栋准备使用线性同余法(Linear Congruential Method)来生成一个随机数列,这种方法需要设置四个非负整数参数 m, a, c, X0,按照下面的公式生成出一系列随机数< Xn >:

Xn+1=(aXn+c) mod m

其中 mod m 表示前面的数除以m的余数。从这个式子可以看出,这个序列的下一个数总是由上一个数生成的。

用这种方法生成的序列具有随机序列的性质,因此这种方法被广泛地使用,包括常用的 C++和 Pascal 的产生随机数的库函数使用的也是这种方法。

栋栋知道这样产生的序列具有良好的随机性,不过心急的他仍然想尽快知道Xn是多少。由于栋栋需要的随机数是0, 1, … ,n−1 之间的,他需要将 Xn 除以g取余得到他想要的数,即 Xn mod g,你只需要告诉栋栋他想要的数Xn mod g是多少就可以了。

Solution

一般一维递推可以用矩阵乘法解决。

然而这题m的规模达到 1018 ,两个不大于 1018 的数相乘不能接受。

那么把乘法改成加法:例如 a×b 就是 b a相加,那么像快速幂那样加一下就可以了。

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define ll long long
using namespace std;
ll b[2][2],a[2],c[2][2];
ll m;
ll q[2][2]={{1,0},{0,1}};
ll mul(ll x,ll y)
{
    ll b=0;
    while(y)
    {
        if(y%2) b=(b+x)%m;
        y/=2;
        x=x*2%m;
    }
    return b;
}
void mul1()
{
    memset(c,0,sizeof(c));
    fo(i,0,1)
    fo(j,0,1)
    fo(k,0,1) c[i][j]=(c[i][j]+mul(q[i][k],b[k][j]))%m;
    memcpy(q,c,sizeof(c));
}
void mul2()
{
    memset(c,0,sizeof(c));
    fo(i,0,1)
    fo(j,0,1)
    fo(k,0,1) c[i][j]=(c[i][j]+mul(b[i][k],b[k][j]))%m;
    memcpy(b,c,sizeof(c));
}
void pow(ll n)
{
    while(n)
    {
        if(n%2) mul1();
        n/=2;
        mul2();
    }
}
int main()
{
    ll n,g;
    scanf("%lld %lld %lld %lld %lld %lld",&m,&b[0][0],&a[1],&a[0],&n,&g);
    b[1][0]=b[1][1]=1;
    pow(n);
    ll ans=0;
    fo(i,0,1)
    ans=(ans+mul(a[i],q[i][0]))%m;
    printf("%lld",ans%g);
}
posted @ 2017-02-11 20:01  sadstone  阅读(54)  评论(0编辑  收藏  举报