Luogu4774 NOI2018 屠龙勇士

16 篇文章 0 订阅
9 篇文章 0 订阅

题面luogu4774

题意:有 n n n条龙,初始生命值 a i a_i ai,恢复能力 p i p_i pi,生命值为负时会恢复,当它生命值恰好为 0 0 0时死亡。初始有 m m m把剑,每把剑有攻击力。每次会选择攻击力 ≤ \le 龙初始生命值且攻击力最大的剑,若不存在则选择攻击力最小的剑。当击杀一条龙时,使用的剑会消失,同时会奖励一把新的剑。现在按照 1 → n 1 \to n 1n的顺序杀龙,固定对每条龙攻击 x x x次,求最少攻击次数。 n ≤ 1 0 5 n \le 10^5 n105,所有 p i p_i pi的最小公倍数 ≤ 1 0 12 \le 10^{12} 1012 a i ≤ 1 0 12 a_i \le 10^{12} ai1012,攻击力 ≤ 1 0 6 \le 10^6 106。保证 p i ≤ a i p_i \le a_i piai p i = 1 p_i = 1 pi=1

题解:首先可以用multiset求出击杀每条龙的剑的攻击值。设杀第 i i i条龙的剑的攻击值为 a t k i atk_i atki

首先特判 p i = 1 p_i = 1 pi=1的部分分,即求 max ⁡ ⌈ a i a t k i ⌉ \max \lceil \frac {a_i} {atk_i} \rceil maxatkiai

现在即求满足该方程组的最小解 x x x a t k i ⋅ x ≡ a i m o d    p i atk_i \cdot x \equiv a_i \mod p_i atkixaimodpi

因为 p i p_i pi不一定为质数,因此考虑使用扩展中国剩余定理。

但是扩展中国剩余定理的形式是: x ≡ a i m o d    b i x \equiv a_i \mod b_i xaimodbi

考虑对原式进行变形。

x x x即为 a t k i ⋅ x − p i ⋅ y = a i atk_i \cdot x - p_i \cdot y = a_i atkixpiy=ai的解

设该方程求出的特解为 s x sx sx

那么 x = s x + k ⋅ p i g c d ( a t k i , p i ) x = sx + k \cdot \frac {p_i} {gcd (atk_i, p_i)} x=sx+kgcd(atki,pi)pi k ∈ Z k \in Z kZ

因此 x ≡ s x m o d    p i g c d ( a t k i , p i ) x \equiv sx \mod \frac {p_i} {gcd (atk_i, p_i)} xsxmodgcd(atki,pi)pi。即化为了扩展中国剩余定理的标准形式。

乘法会爆long long,注意使用快速乘

注意特判 a t k i = p i atk_i = p_i atki=pi a i ̸ = 0 a_i \not= 0 ai̸=0的情况。

#include <bits/stdc++.h>
using namespace std ;
typedef long long ll ;
inline ll read() {
    ll x = 0, f = 1; char c; c = getchar() ;
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar() ;
    return x * f ;
}
inline ll mul (ll x, ll y, ll mod) {
    ll res = 0 ;
    while (y) {
        if (y & 1) res = (res + x) % mod ;
        x = (x + x) % mod; y >>= 1;
    }
    return res ;
}
ll exgcd (ll a, ll b, ll &x, ll &y) {
    if (b == 0) {
        x = 1; y = 0; return a ;
    } else {
        ll d = exgcd (b, a % b, y, x) ;
        y -= (a / b) * x ;
        return d ;
    }
}
const int maxn = 1e5 + 10 ;
int n, m ;
bool flagp ;
ll a[maxn], p[maxn], award[maxn], atk[maxn] ;
multiset<ll> s ;
ll b[maxn], c[maxn] ;
bool vis[maxn] ;
inline ll excrt () {
    ll x = b[1], M = c[1] ;
    for (int i = 2; i <= n; i ++) {
        ll tmpx, tmpy ;
        ll d = exgcd (M, c[i], tmpx, tmpy) ;
        ll f = ((b[i] - x) % c[i] + c[i]) % c[i] ;
        if (f % d != 0) return -1 ;
        tmpx = mul (tmpx, f / d, c[i] / d) ;
        x += tmpx * M ;
        M *= c[i] / d; x = (x % M + M) % M ;
    }
    return x ;
}
inline void solve () {
    if (flagp) {
        ll ans = 0 ;
        for (int i = 1; i <= n; i ++) {
            ll cur = a[i] / atk[i] ;
            if (a[i] % atk[i]) cur ++ ;
            ans = max (ans, cur) ;
        }
        printf("%lld\n", ans) ;
    } else {
        for (int i = 1; i <= n; i ++) {
            atk[i] %= p[i]; a[i] %= p[i] ;
            if (!atk[i] && a[i]) {
                printf("-1\n"); return ;
            }
            ll x, y ;
            ll d = exgcd (atk[i], p[i], x, y) ;
            if (a[i] % d != 0) {
                printf("-1\n"); return ;
            }
            ll sx = (mul (x, a[i] / d, p[i]) + p[i]) % p[i]; //注意快速乘
            b[i] = sx % (p[i] / d) ; c[i] = p[i] / d ;
        }
        printf("%lld\n", excrt ()) ;
    }
}
int main() {
    int T = read() ;
    while (T --) {
        memset (vis, 0, sizeof vis) ;
        s.clear() ;
        n = read(); m = read() ;
        flagp = 1 ;
        for (int i = 1; i <= n; i ++) a[i] = read() ;
        for (int i = 1; i <= n; i ++) {
            p[i] = read() ;
            if (p[i] != 1) flagp = 0 ;
        }
        for (int i = 1; i <= n; i ++) award[i] = read() ;
        for (int i = 1; i <= m; i ++) {
            int atk = read() ;
            s.insert (atk) ;
        }
        for (int i = 1; i <= n; i ++) {
            multiset<ll>::iterator it = s.upper_bound (a[i]) ;
            if (it == s.begin()) atk[i] = (*it) ;
            else atk[i] = *(-- it) ;
            s.erase (it); s.insert (award[i]) ;
        }
        solve () ;
    }
    return 0 ;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值