题解 P3868 【[TJOI2009]猜数字】

lahlah

2019-01-03 10:09:04

Solution

我扔:https://www.luogu.org/problemnew/show/P3868 # [在blog体验好很多哦(真的)](https://blog.csdn.net/qq_38944163/article/details/85677874) ### 题意 ![233](https://i.loli.net/2019/01/03/5c2d6f94bdd9b.png) 就是求这个 $x$ 裸的中国剩余定理,什么?你不知道中国剩余定理。好吧,我讲讲。 首先,看上面那一坨式子,要满足 #### $m_1, m_2, m_3, ...., m_k$两两互质 那么我们设 $M = m1 \times m2 \times m3 \times .... \times mk$ $M_i = M / m_i$ 则对于$x \equiv a_i (\mod m_i)$, 我们可以先求出 $M_i$ 在 $\mod m_i$ 意义下的逆元(扩欧就行了) 设逆元为$M_i^{-1}$ 可得$M_i \times M_i^{-1} \equiv 1 (\mod m_i)$ 所以$a_i \times M_i \times M_i^{-1} \equiv a_i (\mod m_i)$ 又因为 $M_i = M / m_i$ 所以对于所有的 $j \neq i$ ($j$ 不等于 $i$) $a_i \times M_i \times M_i^{-1} \equiv 0(\mod m_j)$ 所以只需要把所有的 $a_i \times M_i \times M_i^{-1}$ 加起来就是答案了 ```cpp int crt(){ int M = 1, x, y, ans = 0; for(int i = 1; i <= n; i ++) M = M * b[i]; //求M for(int i = 1; i <= n; i ++){ int m = M / b[i]; //即Mi exgcd(b[i], m, x, y); //求逆元,求得为y ans =(ans + y * m * a[i]) % M;//累加答案 } if(ans < 0) ans += M;//判断一下 return ans; } ``` 那对于这题呢? ~~Ctrl + c + Ctrl + v~~ 直接套板子? ## ${\color{red} WA \ on \ \#10}$ ## ${\color{white} WTF??}$ # ???? 注意题目中的一句话 #### 所有数据中,第一组数字的绝对值不超过$10^9$(可能为负数),第二组数字均为不超过$6000$的正整数,且第二组里所有数的乘积不超过$10^{18}$ # $10^{18}$ ??!! 乘起来不就 $10^{36}$ 爆long long辣 那怎么办 于是就~~翻题解~~学习了一个叫做快速乘的东西。 快速乘就是用来处理在爆long long ~~的边缘来回试探~~ 的乘法下要取模的一种~~骚~~操作 ~~其实应该叫做龟速乘~~ 核心思想就是把一个数按二进制拆分,然后一位一位对应乘。 看代码感性理解一下吧: ```cpp int ksc(int a, int b, int mod){ int ans = 0; for(;b; b >>= 1, a = (a + a) % mod) if(b&1) ans = (ans + a) % mod; //解释一下, 是把 b 按二进制位拆分, a = a + a 就是每次将 a 乘 2(b每次除2, a肯定要对应乘2嘛) //如果b当前位为1,就对答案有贡献 return ans; } ``` 然后这题就解决了 # ${\color{red} ma?}$ ## ${\color{blue} TLE \ on \ \# 2}$ # ${\color{white} WTF???}$ 哦,忘记说了,~~由于出题人太毒瘤~~ 输入的$a_i$可能为负数,所以在做快速乘之前要把它转为正数 没了? 没了。 等等忘记贴代码了: ```cpp #include<bits/stdc++.h> #define int long long using namespace std; int exgcd(int a, int b, int &x, int &y){//扩欧 if(!b) {x = 1, y = 0; return a;} int d = exgcd(b, a % b, x, y); int t = x; x = y; y = t - a / b * y; return d; } int ksc(int a, int b, int mod){//快速乘 int ans = 0; for(;b; b >>= 1, a = (a + a) % mod) if(b&1) ans = (ans + a) % mod; return ans; } int a[15], b[15], n; int crt(){ int M = 1, x, y, ans = 0; for(int i = 1; i <= n; i ++) M = M * b[i];//算 M for(int i = 1; i <= n; i ++){ int m = M / b[i]; exgcd(b[i], m, x, y);//求逆元 y = (y % b[i] + b[i]) % b[i]; ans =(ans + ksc(y, ksc(m, (a[i] + M) % M, M), M) + M) % M;//快速乘,记得a[i]要转为正数 } if(ans < 0) ans += M; return ans; } main(){ scanf("%lld", &n); for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]); for(int i = 1; i <= n; i ++) scanf("%lld", &b[i]); printf("%lld", crt()); return 0; } ``` ## ${\color{white} enough?}$ ~~我是不会告诉你们空白的地方还有字的~~