【NOI 2018】屠龙勇士(扩欧)
题意理解错了。。。
一把剑打一条龙,打了$x$次后如果龙不死,你就Game Over了。
显然,面对每条龙使用的剑是固定的,如果所有龙中有一条没打死你就挂了。
可以知道,可行的答案集合就是所有龙的可行集合的交集。
考虑当前面对第$i$条龙,若要打死它,$x$满足条件:$a_{i} - x * v_{i} + y * p_{i} = 0$,其中$a$是血量,$v$是攻击力,$p$是恢复力,$y$是某非负整数。
可以把它转化为同余式:$a_{i} \equiv x * v_{i} (mod \; p_{i})$。我们要找出满足该条件的$x$的最小正整数值。
通常情况下,只要先算$v_{i}$在$(mod \; p_{i})$意义下的逆元即可,但此时两者不互质,不一定存在逆元,根据数论某定理:$ a * c \equiv b * c (mod \; m) \Rightarrow a \equiv b (mod \; m / ( c , m ) $,我们先除去三者的$gcd$,再求逆元就可以了,如果此时仍然没有逆元,则无解。(这里有一个要注意的事情,当$p_{i}=1$的时候会出点问题,特判就可以了)
可以发现,$x$可以的取值是一个等差数列,我们需要把$n$个等差数列合并起来,取它们的交集。
假设我们即将合并的两个等差数列为$(a_{s}, d_{s}),(a_{i},d_{i})$,其中$a$为首项,$d$为公差。
合并后的公差比较好算:$lcm(d_{s},d_{i})$。主要考虑如何求第一个数同时出现在两个等差数列中了。
可以列出以下等式:$a_{s} + x * d_{s} = a_{i} + y * d_{i}$,其中$x, y$都是某个非负整数。
移项转化为同余式:$x * d_{s} \equiv a_{i} - a_{s} (mod \; d_{i})$,和刚刚的套路一样就可以解出$x$的值啦。
最后显然$a_{s}$就是我们想要的答案。(提示:由于中间答案过大,使用中精度存储,也可以用快速乘)
Update :
求同余式$x * a \equiv b (mod \; p)$的另一种思路,设$d = gcd(a, p)$,那有解当且仅当$d | b$。具体证明就是扩展欧几里得相关。
那该同余方程等价于$x * \frac{a}{d} \equiv \frac{b}{d} (mod \; \frac{p}{d})$。此时由于$\frac{b}{d}$一定与$\frac{p}{d}$互质,通过求逆元直接解出$x$即可。
$\bigodot$技巧&套路:
- 基础数论,同余式$x * a \equiv b (mod \; p)$求解的技巧。
#include <cstdio> #include <set> typedef long long LL; const int N = 100005; int tc, n, m; LL ai[N], di[N], a[N], p[N], b[N], v[N]; std::multiset<LL> S; inline LL Mul(LL a, LL b, LL p) { a %= p; b %= p; LL a0 = a & 1048575, a1 = a >> 20; return (((a1 * b % p) << 20) + a0 * b) % p; } LL Ex_gcd(LL a, LL b, LL &x, LL &y) { if (b == 0) return x = 1, y = 0, a; LL g = Ex_gcd(b, a % b, y, x); y -= a / b * x; return g; } inline LL Get(LL x, LL re = 0) { auto it = S.upper_bound(x); if (it != S.begin()) --it; re = *it; S.erase(it); return re; } int main() { scanf("%d", &tc); for (; tc; --tc) { S.clear(); scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]); for (int i = 1; i <= n; ++i) scanf("%lld", &p[i]); for (int i = 1; i <= n; ++i) scanf("%lld", &b[i]); for (int i = 1; i <= m; ++i) scanf("%lld", &v[i]), S.insert(v[i]); LL as = 1, ps = 1; for (int i = 1; i <= n; ++i) { LL vi = Get(a[i]), x, y, ai, pi; LL d = Ex_gcd(vi, p[i], x, y); if (a[i] % d != 0) { as = -1; break; } x = (x % (p[i] / d) + p[i] / d) % (p[i] / d); ai = Mul(a[i] / d, x, p[i] / d); pi = p[i] / d; if (p[i] == 1) ai = a[i] / vi + (bool)(a[i] % vi); S.insert(b[i]); if (pi == 1) { if (as < ai) { LL bl = (ai - as) / ps + (bool)((ai - as) % ps); as = as + bl * ps; } continue; } LL dir = ((ai - as) % pi + pi) % pi; d = Ex_gcd(ps, pi, x, y); if (dir % d != 0) { as = -1; break; } x = (x % (pi / d) + pi / d) % (pi / d); LL nx = Mul(dir / d, x, pi / d); as = as + nx * ps; ps = ps / d * pi; } printf("%lld\n", as); } return 0; }