【NOI2018】屠龙勇士(中国剩余定理)

题目链接:【NOI2018】屠龙勇士

首先,我们可以用 multiset m u l t i s e t 求出杀死每一条龙所用的剑。设它的攻击力为 attacki a t t a c k i 。然后,我们发现题目转化成了:求

attackixaimodpi a t t a c k i ⋅ x ≡ a i mod p i

的最小非负整数解。

我们将 attacki a t t a c k i 移到等式右边,式子转化为

xiaiattack1imodpi x i ≡ a i ⋅ a t t a c k i − 1 mod p i

这就构成了 扩 展 中 国 剩 余 定 理 的一般形式。直接求解即可。

此题的细节较多,请注意以下几点:

  • attacki a t t a c k i pi p i 不互素的情况下无法快速求出 attack1i a t t a c k i − 1 。这时,根据模运算的消去律,可以将 ai,pi,attacki a i , p i , a t t a c k i 同时除以 gcd(ai,pi,attacki) g c d ( a i , p i , a t t a c k i ) 。此时若 attacki a t t a c k i pi p i 仍不互素,则一定无解。
  • 在求解答案时可能需要对两个 1012 10 12 级别的数进行乘法取模运算,此时需要用到 “快速乘”(倍增乘法)。
  • 最后算出来的答案不一定能使得所有龙的体力值降到非正值,所以要将答案不停的加上 lcmni=1pi l c m i = 1 n p i 直到合法。
#include <cstdio>
#include <set>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 100005;
int T, n, m;
ll a[maxn], p[maxn], atk[maxn], sword[maxn];
void upd(ll &x, ll y, ll mod) {
    if ((x += y) >= mod) {
        x -= mod;
    }
}
ll mult(ll a, ll b, ll mod) {
    a = (a % mod + mod) % mod;
    b = (b % mod + mod) % mod;
    ll res = 0;
    for (; b; b >>= 1, upd(a, a, mod)) {
        if (b & 1) {
            upd(res, a, mod);
        }
    }
    return res;
}
ll gcd(ll a, ll b) {
    return b ? gcd(b, a % b) : a;
}
void exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) {
        x = 1, y = 0;
        return;
    }
    exgcd(b, a % b, y, x);
    y -= a / b * x;
}
ll inverse(ll a, ll mod) {
    ll x, y;
    exgcd(a, mod, x, y);
    return (x % mod + mod) % mod;
}
ll excrt(ll *A, ll *B, ll *M, ll &res) {
    res = 0;
    ll lcm = 1;
    for (int i = 1; i <= n; i++) {
        ll a = mult(A[i], lcm, M[i]);
        ll b = B[i] - mult(A[i], res, M[i]);
        ll d = gcd(a, M[i]);
        if (b % d != 0) {
            res = -1;
            return 0;
        }
        ll x = mult(b / d, inverse(a / d, M[i] / d), M[i] / d);
        res += lcm * x, lcm *= M[i] / d;
    }
    res = (res % lcm + lcm) % lcm;
    return lcm;
}
int main() {
    for (scanf("%d", &T); T--; ) {
        multiset<ll> S;
        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", atk + i);
        }
        for (int i = 1; i <= m; i++) {
            ll x;
            scanf("%lld", &x);
            S.insert(x);
        }
        ll mx = 0;
        for (int i = 1; i <= n; i++) {
            multiset<ll>::iterator it = S.upper_bound(a[i]);
            if (it != S.begin()) {
                it--;
            }
            sword[i] = (*it), S.erase(it), S.insert(atk[i]);
            mx = max(mx, (a[i] + sword[i] - 1) / sword[i]);
        }
        ll x, lcm = excrt(sword, a, p, x);
        if (x != -1 && x < mx) {
            x += lcm * ((mx - x + lcm - 1) / lcm);
        }
        printf("%lld\n", x);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值