【NOI2018】【BZOJ5418】【UOJ396】【LOJ2721】屠龙勇士

【题目链接】

【前置技能】

  • 数据结构(线段树,平衡树,set,map等)
  • 扩展中国剩余定理

【题解】

  • 首先发现,用一个支持单点插入,询问lower_bound的数据结构维护一下,那么每条龙用哪一把剑其实是确定的。
  • 那么问题其实就是求若干个形如 A T K i ∗ x ≡ a i ( m o d   p i ) ATK_i*x \equiv a_i(mod~p_i) ATKixai(mod pi)的同余方程组的解。模数不一定互质,用扩展中国剩余定理合并,最后输出第一个大于等于答案下界的解。注意其中部分运算可能会爆longlong,需要使用快速乘。
  • 时间复杂度 O ( Q N l o g N ) O(QNlogN) O(QNlogN)

【代码】

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL  long long
#define MAXN    100010
#define IT  multiset <LL> :: iterator
using namespace std;
int Q, n, m;
LL a[MAXN], p[MAXN], b[MAXN], t[MAXN], x, y, C[MAXN], M[MAXN], lim;
multiset <LL> s;
 
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void read(T &x){
    x = 0; int f = 1; char ch = getchar();
    while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
    while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
    x *= f;
}
 
void exgcd(LL a, LL b, LL &x, LL &y){
    if (b == 0) {x = 1, y = 0; return;}
    LL q = a / b, r = a % b;
    exgcd(b, r, y, x);
    y -= q * x;
}
 
LL gcd(LL a, LL b){
    if (a == 0 || b == 0) return a + b;
    else return gcd(b, a % b);
}
 
LL inv(LL a, LL b){
    exgcd(a, b, x, y);
    x = x % b;
    if (x < 0) x += b;
    return x;
}
 
LL mul(LL a, LL b, LL mod){
    a %= mod, b %= mod;
    if (a < 0) a += mod;
    if (b < 0) b += mod;
    LL ret = 0;
    while (b){
        if (b & 1) ret = (ret + a) % mod;
        a = (a + a) % mod;
        b >>= 1;
    }
    if (ret < 0) ret += mod;
    return ret;
}
 
LL excrt(int n){
    int fla = 1;
    for (int i = 2; i <= n; ++i){
        LL m1 = M[i - 1], m2 = M[i], c1 = C[i - 1], c2 = C[i], tmp = gcd(m1, m2);
        if ((c2 - c1) % tmp) {fla = 0; break;}
        M[i] = m1 / tmp* m2;
        LL tnp = inv(m1 / tmp, m2 / tmp), tp = ((c2 - c1) / tmp);
        tnp = mul(tnp, tp, (m2 / tmp));
        C[i] = mul(tnp, m1, M[i]);
        C[i] = (C[i] + c1) % M[i];
        if (C[i] < 0) C[i] = C[i] + M[i];
    }
    if (fla) {
        if (C[n] >= lim) return C[n];
        else {
            LL ret = C[n];
            ret = ret + ((lim - ret) / M[n]) * M[n];
            while (ret < lim) ret += M[n];
            while (ret - M[n] >= lim) ret -= M[n];
            return ret;
        }
    } else return -1;
}
 
int main(){
    read(Q);
    while (Q--){
        read(n), read(m);
        for (int i = 1; i <= n; ++i)
            read(a[i]);
        for (int i = 1; i <= n; ++i)
            read(p[i]);
        for (int i = 1; i <= n; ++i)
            read(b[i]);
        for (int i = 1; i <= m; ++i)
            read(t[i]);
        s.clear();
        for (int i = 1; i <= m; ++i)
            s.insert(t[i]);
        int fla = 1;
        lim = 1;
        int cnt = 0;
        for (int i = 1; i <= n; ++i){
            ++cnt;
            IT tmp = s.begin();
            if ((*tmp) < a[i]) {
                tmp = s.end(); tmp--;
                if ((*tmp) > a[i]){
                    tmp = s.upper_bound(a[i]);
                    tmp--;
                }
            }
            LL cur = *tmp;
            s.erase(tmp);
            s.insert(b[i]);
            if (p[i] == 1){
                LL tmp = a[i] / cur;
                if (a[i] % cur) ++tmp;
                chkmax(lim, tmp);
                --cnt;
            } else {
                LL tmp = a[i] / cur;
                if (a[i] % cur) ++tmp;
                chkmax(lim, tmp);
                LL d = gcd(cur, p[i]);
                if (a[i] % d) {printf("-1\n"); fla = 0; break;}
                LL tt = cur / d, aa = a[i] / d, pp = p[i] / d;
                C[cnt] = mul(aa, inv(tt, pp), pp), M[cnt] = pp;
            }
        }
        if (fla) {
            if (cnt == 0) printf("%lld\n", lim);
            else printf("%lld\n", excrt(cnt));
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值