[NOI2018] 屠龙勇士

题面

屠龙勇士

屠龙少年终成恶龙

设次数为 \(x\),攻击第 \(i\)\(boss\) 时所选的剑的攻击力为 \(atk_i\),第 \(i\)\(boss\) 的血量为 \(hp_i\)
根据题设可知,要杀死一个 \(boss\) 必须满足 \(p_i \mid hp_i - x \times atk_i\), 且 \(hp_i - x \times atk_i < 0\)
而其中的 \(atk_i\) 是未知的,但是可以预处理出来,因为每次打 \(boss\) 的时候选择的剑都是唯一的。
所以我们只用维护一个支持动态插入和查询小于等于某个值的最大的值的值的数据结构即可,显然平衡树可以支持,但是显然不会手写平衡树,所以直接用 \(set\)
但是 \(set\) 是会去重的,和我们想要的不太一样,所以用支持可重元素的 \(set\)\(multiset\) 维护即可,至于查询,熟悉的话直接就知道是 \(*(--upper\_ bound())\),判一下返回的迭代器是不是 \(begin()\),如果是那么就是题目规定中的第二种情况,\(*(begin())\) 即可。
所以现在我们的柿子里面的值都是已知的了,我们于是来化一下柿子(后面有模数的,其中的等号表示同余符号)。

\[p_i \mid hp_i - x \times atk_i \]

\[hp_i - x \times atk_i = 0\pmod {p_i} \]

\[atk_i \times x = hp_i\pmod {p_i} \]

化到这里,如果没有前面的系数 \(atk_i\) 的话就是我们熟悉的中国剩余定理了,但是我们又不能把系数除掉,因为 \(atk_i\) 在模 \(p_i\) 意义下不一定有逆元,那么我们就要用到扩展中国剩余定理的扩展。
其实就是仿照扩展中国剩余定理的推导过程推导一遍就可以了。
考虑第 \(k\) 个方程,设已经求出了前 \(k - 1\) 个方程的通解 \(x\),记 \(m = lcm(m_1, m_2, …, m_{k - 1})\), 那么 $x + i \times m $ 显然也是前 \(k - 1\) 个方程的通解,那么我们就是要求出一个整数 \(t\),让答案满足第 \(k\) 个方程,
也就是 \(atk_i \times (x + t \times m) = hp_i \pmod {p_i}\),把左边拆开,把已知项移到右边得:

\[atk_i \times m \times t = hp_i - atk_i \times x \pmod {p_i} \]

再继续仿照扩展中国剩余定理推导,套一个扩展欧拉里得:

\[(atk_i \times m) \times t + p_i \times y = hp_i - atk_i \times x \]

现在 \(t\) 的求法就显然了,\(t\) 前的系数看做 \(a\), \(p_i\) 看做 \(b\), \(hp_i - atk_i \times x\) 看做 \(c\)
扩欧求出 \(gcd\) 之后同样是判断 \(gcd\) 是否整除 \(c\), 判断线性同余方程是否有解即可。
然后再根据线性同余方程解的求法,求出 \(t\) 即可,把 \(t \times m\) 累加到答案中,然后更新 \(m\), 再对答案取模。

小细节

能先除的地方尽量先除,乘的地方有模数尽量用快速乘,本题极易溢出。

code

#include<cstdio>
#include<set>
#include<iostream>
#include<cctype>

using namespace std;

typedef long long LL;

const int N = 1e5 + 5;

int t, n, m; LL hp[N], atk[N], p[N], add[N], Min;

multiset < LL > :: iterator it;

multiset < LL > s;

template < typename T > inline void read(T &x) {
	x = 0; int c = getchar();
	for(; !isdigit(c); c = getchar());
	for(; isdigit(c); c = getchar())
		x = x * 10 + c - 48;
}

inline LL mul(LL a, LL b, LL mod) {
    LL L = a * (b >> 25LL) % mod * (1LL << 25) % mod;
    LL R = a * (b & ((1LL << 25) - 1)) % mod;
    return (L + R) % mod;
}

LL exgcd(LL a, LL b, LL &x, LL &y) {
	if(!b) { x = 1, y = 0; return a; }
	LL d = exgcd(b, a % b, x, y), z;
	z = x, x = y, y = z - a / b * y;
	return d;
}

inline LL CRT() {
	LL M = 1, res = 0, A, B, C, D, x, y;
	for(int i = 1; i <= n; i++) {
		A = mul(atk[i], M, p[i]), B = p[i], C = ((hp[i] - mul(atk[i], res, p[i])) % p[i] + p[i]) % p[i];
		D = exgcd(A, B, x, y);
		if(C % D) return -1;
		A /= D, B /= D, C /= D;
		x = mul(x, C, B);
		res += x * M, M *= p[i] / D, res  = (res % M + M) % M;
	}
	if(res < Min) res += ((Min - res - 1) / M + 1) * M;
	return res;
}

inline void solve() {
	read(n), read(m);
	s.clear(); Min = 0;
	for(int i = 1; i <= n; i++) read(hp[i]);
	for(int i = 1; i <= n; i++) read(p[i]);
	for(int i = 1; i <= n; i++) read(add[i]);
	for(int i = 1, x; i <= m; i++) read(x), s.insert(x);
	for(int i = 1; i <= n; i++) {
		it = s.upper_bound(hp[i]);
		if(it != s.begin()) --it;
		atk[i] = (*it); s.erase(it);
		s.insert(add[i]);
		Min = max(Min, (hp[i] - 1) / atk[i] + 1);
	}
	printf("%lld\n", CRT());
}

int main() {
	read(t);
	while(t--) solve();
	return 0;
}
posted @ 2021-08-27 19:53  init-神眷の樱花  阅读(55)  评论(0)    收藏  举报