luogu4774 [NOI2018]屠龙勇士

本文探讨了如何通过预处理所有可能的刀具选择,并利用EXCRT(中国剩余定理扩展)来解决一组同余方程,特别关注于解决涉及龙的生命值和攻击力的复杂场景。文章详细解释了使用exgcd求解同余方程的过程,以及如何正确处理可能非质数的模数。同时,强调了快速乘法在避免数据溢出中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

  • 可以想到第一次屠龙用的刀是确定的,可以列一个同余方程 x ∗ a t k 1 − a 1 = 0 ( m o d   p 1 ) x * atk_1 - a_1 = 0 (mod \space p_1) xatk1a1=0(mod p1) ,然后每次用的刀都可以确定,因此预先确定所有的刀,对100%的数据用EXCRT求解同余方程组就可以了。

  • 怎么预先确定所有的刀呢?直接写的话可以离散然后用树状数组维护。。。但是有STL
    Multiset里的lower_bound 和upper_bound

  • 使用exgcd求解一般的同余方程 B x = A ( m o d   p ) Bx=A (mod \space p) Bx=A(mod p)这里p可能不是质数,B和p可能不互质,上式可化成
    B x + P y = A Bx+Py=A Bx+Py=A
    从而用exgcd解出x即可。

  • EXCRT的注意事项:数据范围都是long long的,直接乘会爆long long, 要用快速乘

  • 这个题的坑:用EXCRT直接求出来的最小正整数解可能不是答案,考虑同余方程之外,题目要求龙的生命值(经过恢复)恰好为0才会死去,因此每条龙都有最小攻击次数。答案应该是通解中满足>=max{最小攻击次数}的值。


只是做到理解具体步骤的程度,还不能完全自己写出来,STL几乎不会用,同余化简要推很久,exgcd,excrt只有有板子的时候才会写,会忘记写快速乘

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <set>
using namespace std;
#define N 100005
typedef long long LL;
int T,n,m;
LL a[N],p[N],btk[N],atk[N],c[N], P;
LL qmul(LL a, LL b, LL P)
{
	LL res = 0;
	while (b)
	{
		if (b & 1) res = (res + a) % P;
		a = (a + a) % P;
		b >>= 1;
	}
	return res;
}
LL exgcd(LL a, LL b, LL & x, LL & y)
{
	if (b == 0) {x = 1;y = 0;return a;}
	LL d = exgcd(b, a % b, y, x);
	y -= (a / b) * x;
	return d;
}
LL excrt()
{
	LL M = p[1], ans = a[1], x, y;
	for (int i=1;i<=n;i++)
	{
		LL d = exgcd(M, p[i], x, y), c = ((a[i] - ans) % p[i] + p[i]);
		LL tmp = p[i] / d;
		if (c % d != 0) return -1;
		x = qmul(x, c / d, tmp);
		ans += x * M;
		M = M * tmp;
		ans = (ans % M + M) % M;
	}
	P = M;
	return ans;
}
LL solve()
{
	multiset<LL> S;
	for (int i=1;i<=m;i++)
		S.insert(atk[i]);
	multiset <LL>:: iterator it;
	LL tmp = 0;
	for (int i=1;i<=n;i++){
		it = (a[i] < (*S.begin())) ? S.begin():(--S.upper_bound(a[i]));
		c[i] = *it;
		S.erase(it);
		S.insert(btk[i]);
		tmp = max(tmp, ((a[i]-1) / c[i] + 1));//上取整
	}
	//化简同余方程
	for (int i=1;i<=n;i++)
	{
		LL x, y;
		LL d = exgcd(c[i], p[i], x, y);
		if (a[i] % d != 0) return -1;
		x = (x % p[i] + p[i]) % p[i];
		a[i] = qmul(x, a[i] / d, p[i]);//! 还是在模p[i]的意义下
		p[i] /= d;
	}
	LL ans = excrt();
	if (ans != -1 && ans < tmp)
		ans = ans + ((tmp - ans - 1) / P + 1) * P;//上取整
	return ans;
}
int main()
{
	scanf("%d", &T);
	while (T--)
	{
		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", &btk[i]);
		for (int i=1;i<=m;i++) {scanf("%lld", &atk[i]);}
		printf("%lld\n", solve());
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值