洛谷 P3306 【随机数生成器】
- 题库:洛谷
- 题号:3306
- 题目:随机数生成器
- link:https://www.luogu.com.cn/problem/P3306
观察题目,易得出暴力代码,但是会TLE,只能得20pts
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 int T; 5 ll a, b, x1, t, p, ans; 6 int main() 7 { 8 scanf("%d", &T); 9 while(T--) 10 { 11 ans = 0; 12 scanf("%lld %lld %lld %lld %lld", &p, &a, &b, &x1, &t); 13 while(233) 14 { 15 ++ans; 16 if(ans > p) 17 { 18 puts("-1"); 19 break; 20 } 21 if(x1 == t) 22 { 23 printf("%lld\n", ans); 24 break; 25 } 26 x1 = (x1 * a + b) % p; 27 } 28 } 29 return 0; 30 }
考虑展开式子。
由$x[i$ $+$ $1]$ $≡$ $a$ $*$ $x[i]$ $+$ $b$ $(mod$ $p)$得
$x[1]$ $=$ $x1$ $\%$ $p;$
$x[2]$ $=$ $(a$ $*$ $x1$ $+$ $b)$ $\%$ $p;$
$x[3]$ $=$ $(a$2 $*$ $x1$ $+$ $a$ $*$ $b$ $+$ $b)$ $\%$ $p;$
$x[4]$ $=$ $(a$3 $*$ $x1$ $+$ $a$2 $*$ $b$ $+$ $a$ $*$ $b$ $+$ $b)$ $\%$ $p;$
$x[5]$ $=$ $(a$4 $*$ $x1$ $+$ $a$3 $*$ $b$ $+$ $a$2 $*$ $b$ $+$ $a$ $*$ $b$ $+$ $b)$ $\%$ $p;$
$……$
$x[k]$ $=$ $(a$k - 1 $*$ $x1$ $+$ $a$k - 2 $*$ $b$ $+$ $a$k - 3 $*$ $b$ $+$ $......$ $+$ $a$2 $*$ $b$ $+$ $a$ $*$ $b$ $+$ $b)$ $\%$ $p;$
题目中求在第几天看到了第$t$页,如果永远也看不到就输出$-1$,否则输出最早的一天。
不妨设在第$k$天看到了第$t$页,于是可得方程:
$x[k]$ $≡$ $t$ $(mod$ $p);$
$a$k - 1 $*$ $x1$ $+$ $a$k - 2 $*$ $b$ $+$ $a$k - 3 $*$ $b$ $+$ $......$ $+$ $a$2 $*$ $b$ $+$ $a$ $*$ $b$ + $b$ $≡$ $t$ $(mod$ $p);$
其中,$a、x1、b、t、p$已知,求解$k$的值。
先不看第一项$a$k - 1 $*$ $x1$,容易发现其余项是一个等比数列。
提出$b$:
$a$k - 1 $*$ $x1$ $+$ $b$ $*$ $(a$k - 2 $+$ $a$k - 3 $+$ $......$ $+$ $a$2 $+$ $a$ $+$ $1)$ $≡$ $t$ $(mod$ $p);$
求解$a$k - 2 $+$ $a$k - 3 $+$ $......$ $+$ $a$2 $+$ $a$ $+$ $1$的值可以代入等比数列求和公式。
解得:
当$a$ $=$ $1$时,原式 $=$ $k$ $-$ $1$;
当$a$ $≠$ $1$时,原式 $=$ $(1$ $-$ $a$k - 1) $/$ $(1$ $-$ $a);$
代入原方程可得:
当$a$ $=$ $1$时,原方程为:
$x1$ $+$ $b$ $*$ $(k$ $-$ $1)$ $≡$ $t$ $(mod$ $p);$
$x1$ $+$ $bk$ $-$ $b$ $≡$ $t$ $(mod$ $p);$
$bk$ $≡$ $t$ $+$ $b$ - $x1$ $(mod$ $p);$
当$a$ $≠$ $1$时,原方程为:
$a$k - 1 $*$ $x1$ $+$ $b$ $*$ $(1$ $-$ $a$k - 1$)$ $/$ $(1$ $-$ $a)$ $≡$ $t$ $(mod$ $p);$
$a$k - 1 $*$ $x1$ $*$ $(1$ $-$ $a)$ $+$ $b$ $*$ $(1$ $-$ $a$k - 1$)$ $≡$ $t$ $*$ $(1$ $-$ $a)$ $(mod$ $p);$
$a$k - 1 $*$ $(x1$ $-$ $x1$ $*$ $a)$ $+$ $b$ $-$ $b$ $*$ $a$k - 1 $≡$ $t$ $-$ $t$ $*$ $a$ $(mod$ $p);$
$a$k - 1 $*$ $(x1$ $-$ $x1$ $*$ $a$ $-$ $b)$ $≡$ $t$ $-$ $t$ $*$ $a$ $-$ $b$ $(mod$ $p);$
$a$k - 1 $≡$ $(t$ $-$ $t$ $*$ $a$ $-$ $b)$ $*$ $(x1$ $-$ $x1$ $*$ $a$ $-$ $b)$-1 $(mod$ $p);$(这里的$-1$次方表示逆元)
整理得:
当$a$ $=$ $1$时,$bk$ $≡$ $t$ $+$ $b$ $-$ $x1$ $(mod$ $p);$
当$a$ $≠$ $1$时,$a$k - 1 $≡$ $(t$ $-$ $t$ $*$ $a$ $-$ $b)$ $*$ $(x1$ $-$ $x1$ $*$ $a$ $-$ $b)$-1 $(mod$ $p);$
易看出:
当$a$ $=$ $1$时,可以用$exgcd$求解出$k$;
当$a$ $≠$ $1$时,可以用$BSGS$求解出$k$;
有一些需要特判的坑点在代码里:
1 #include <bits/stdc++.h> 2 #define INF 0x3f3f3f3f 3 #define ll long long 4 using namespace std; 5 int T; 6 ll a, b, p, x, y, ps, d, x1, t; 7 map < ll, ll > mp; 8 void exgcd(ll a, ll b, ll &x, ll &y) 9 { 10 if(!b) x = 1, y = 0, d = a; 11 else exgcd(b, a % b, y, x), y -= x * (a / b); 12 return; 13 } 14 ll ksm(ll x, ll y, ll mod) 15 { 16 ll ans = 1; 17 for(; y; y >>= 1) 18 { 19 if(y & 1) ans = ans * x % mod; 20 x = x * x % mod; 21 } 22 return ans; 23 } 24 ll bsgs(ll a, ll b, ll p) 25 { 26 mp.clear();//记得清空 27 b = (b % p + p) % p;//b可能是负数 28 if(a == b) return 1;//特判a == b 29 ps = ceil((double)sqrt(p)); 30 exgcd(ksm(a, ps, p), p, x, y); 31 x = (x % p + p) % p; 32 mp[1] = 0; 33 for(int i = 1, t = a; i <= ps; ++i, t = t * a % p) if(!mp[t]) mp[t] = i;//这里要求先输出最先读到的那一天所以要判空 34 for(int i = 0, xx = 1; i <= ps; ++i, xx = xx * x % p) 35 { 36 if(b * xx % p == 1) return ps * i;//这里由于我前面设了mp[1] = 0所以要特判一下 37 if(mp[b * xx % p]) return ps * i + mp[b * xx % p]; 38 } 39 return -1; 40 } 41 int main() 42 { 43 scanf("%d", &T); 44 while(T--) 45 { 46 scanf("%lld %lld %lld %lld %lld", &p, &a, &b, &x1, &t); 47 if(t == x1)//特判 48 { 49 puts("1"); 50 continue; 51 } 52 if(a == 0)//特判 53 { 54 if(t == b) puts("2"); 55 else puts("-1"); 56 continue; 57 } 58 if(a == 1)//exgcd求 59 { 60 ll c = t + b - x1; 61 c = (c % p + p) % p;//先取模 62 if(!c)//c = 0直接输出p就是了,具体为什么自己把c = 0的式子带进去推 63 { 64 printf("%lld\n", p); 65 continue; 66 } 67 exgcd(b, p, x, y); 68 x = (x % p + p) % p; 69 if(c % d != 0) puts("-1"); 70 else printf("%lld\n", (x * c / d % p + p) % p); 71 continue; 72 }//bsgs求 73 exgcd(((x1 - x1 * a - b) % p + p) % p, p, x, y); 74 x = (x % p + p) % p; 75 ll tt = bsgs(a, t * x - t * a % p * x - b * x, p);//三连乘别忘了取模 76 if(tt != -1) printf("%lld\n", tt + 1); 77 else puts("-1"); 78 } 79 return 0; 80 }
调试代码所用的bat:
:begin maker.exe > P3306.in 随机数生成器.exe < P3306.in > P3306.out brute.exe < P3306.in > P3306.ans fc P3306.out P3306.ans if not errorlevel = 1 goto begin pause
brute就是上面的暴力,maker就是我自己写的一个数据生成器。