Luogu4774 NOI2018 屠龙勇士 ExCRT

传送门


原来NOI也会出裸题啊……

用multiset求出对付每一个BOSS使用的武器威力\(ATK_i\),可以得到\(m\)个式子\(ATK_ix \equiv a_i \mod p_i\)

看起来可以直接魔改式子了……

等一下!如果\(a_i > p_i\)\(ATK_ix<a_i\)没把BOSS打死怎么办QAQ

看数据范围,没有特性1(\(a_i \leq p_i\))的点似乎\(p_i=1\)?那不只要保证攻击次数能够把所有BOSS血量打到\(\leq 0\)就行了,,,于是这个顾虑就消除了(虽然要写数据分治)

考虑上面得到的式子,很像ExCRT,但ExCRT的式子都长\(x \equiv b_i \mod p_i\),这里的式子不长这样。于是考虑改式子。

如果\(gcd(ATK_i , p_i) \not\mid a_i\),显然原式无解。

\(gcd(ATK_i , p_i) \mid a_i\)时,求出\(ATK_ix + p_iy = gcd(ATK_i,p_i)\)的一组解\((x_1,y_1)\),那么\(ATK_ix + p_iy = a_i\)的一组解就是\((\frac{x_1a_i}{gcd(ATK_i,p_i)} , \frac{y_1a_i}{gcd(ATK_i,p_i)})\)

那么\(ATK_ix \equiv a_i \mod p_i\)的通解就是\(x \equiv \frac{x_1a_i}{gcd(ATK_i,p_i)} \mod \frac{p_i}{gcd(ATK_i,p_i)}\)

这个式子长得跟ExCRT的式子相同了,直接套板子即可。

值得注意的是,因为模数可能超过int,导致可能出现乘法爆long long。解决方案是log龟速乘/long double型快速乘/__int128

还有一个细节是:因为\(a_i \leq p_i\)所以最后答案可能是\(0\),此时应该输出的是最后的模数。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

#define int long long
int read(){
    int a = 0;
    char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return a;
}

const int _ = 1e5 + 7;
map < int , int > swr;
int hp[_] , p[_] , atk[_] , Atk[_] , N , M;

void exgcd(int a , int b , int &d , int &x , int &y){
    b == 0 ? (d = a , x = 1 , y = 0) : (exgcd(b , a % b , d , y , x) , y -= a / b * x);
}

int mul(int x , int y , int MOD){
    int sum = 0;
    x %= MOD; y %= MOD;
    while(y){
        if(y & 1) sum = sum + x >= MOD ? sum + x - MOD : sum + x;
        x = x + x >= MOD ? x + x - MOD : x + x;
        y >>= 1;
    }
    return sum;
}

signed main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    for(int T = read() ; T ; --T){
        swr.clear();
        N = read(); M = read();
        for(int i = 1 ; i <= N ; ++i)
            hp[i] = read();
        bool sp = 1;
        for(int i = 1 ; i <= N ; ++i)
            sp &= (p[i] = read()) == 1;
        for(int i = 1 ; i <= N ; ++i)
            atk[i] = read();
        for(int i = 1 ; i <= M ; ++i)
            ++swr[read()];
        for(int i = 1 ; i <= N ; ++i){
            auto t = swr.upper_bound(hp[i]);
            if(t != swr.begin()) --t;
            Atk[i] = t->first;
            if(!--t->second) swr.erase(t);
            ++swr[atk[i]];
        }
        int Mod = 1 , ans = 0;
        if(sp)
            for(int i = 1 ; i <= N ; ++i)
                ans = max(ans , hp[i] / Atk[i] + (bool)(hp[i] % Atk[i]));
        else
            for(int i = 1 ; i <= N ; ++i){
                int a , x , y;
                exgcd(Atk[i] , p[i] , a , x , y);
                if(hp[i] % a){
                    ans = -1;
                    break;
                }
                int mod = p[i] / a;
                if(x < 0) x += mod;
                int tp = mul(x , hp[i] / a , mod);
                int xs = (mod + tp - ans % mod) % mod;
                exgcd(Mod , mod , a , x , y);
                if(xs % a){
                    ans = -1;
                    break;
                }
                int newMod = Mod / a * mod;
                if(x < 0) x += mod / a;
                ans = (mul(mul(x , xs / a , mod / a) , Mod , newMod) + ans) % newMod;
                Mod = newMod;
            }
        cout << (ans ? ans : Mod) << endl;
    }
    return 0;
}

转载于:https://www.cnblogs.com/Itst/p/10350614.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值