【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;
}
View Code

 

posted @ 2018-07-21 12:15  Dance_Of_Faith  阅读(384)  评论(0编辑  收藏  举报