Warrior / 屠龙勇士
题目链接:jzoj 7183 / luogu P4774
题目大意
有 n 条龙,有生命值和恢复值,你攻击一轮之后会不断恢复,如果恢复到生命值恰好为 0 是会死掉,你一开始有 j 个武器,有各自的伤害,且每杀死一个龙会掉一个武器。
然后你打龙会找攻击力不高于龙生命值的最高攻击力武器打,如果没有,就找最低攻击力的。
然后要求每一轮打只能打同样的次数,且一定要搞死龙。
打龙顺序固定,问你这个次数最小是多少次,如果不可能就输出 -1。
思路
首先不难想到每次打龙用的武器是一定的。
然后你考虑怎么求,一看发现好像要平衡树,然后你发现要维护的东西很简单,似乎用 upper_bound 就可以搞。
但你发现它要快速修改,然后你就要用 STL 自带的一个简单平衡树——set。
(我们这里会有重复的,所以是 multiset)
然后我们找到了每次打的武器的攻击力
e
i
e_i
ei,接着看要怎么搞。
不难想到就是要让
x
e
i
≡
a
i
(
m
o
d
p
i
)
xe_i\equiv a_i(\bmod\ p_i)
xei≡ai(mod pi) 满足,找到最小的
x
x
x。
嗯,这很 EXCRT。
但你发现它
x
x
x 系数不是
1
1
1,那你考虑变一下形。(再扩展一下)
假设你求出了前面
i
−
1
i-1
i−1 条合并的,然后
LCM
\text{LCM}
LCM 是
M
M
M,上次的答案是
a
n
s
ans
ans,那通解就是
a
n
s
+
M
x
ans+Mx
ans+Mx,那我们继续考虑。
就是要
e
i
(
a
n
s
+
M
x
)
≡
a
i
(
m
o
d
p
i
)
e_i(ans+Mx)\equiv a_i(\bmod\ p_i)
ei(ans+Mx)≡ai(mod pi)
然后转化:
e
i
M
x
+
p
i
y
=
a
i
−
e
i
×
a
n
s
e_iMx+p_iy=a_i-e_i\times ans
eiMx+piy=ai−ei×ans
接着你发现它就是扩欧,然后就合并,那判断扩欧有没有解就是这个合并有没有解了。
那接着你会发现一个问题,可能你求出来的答案,打怪生命值到不了
0
0
0 以下。
那你只需要一开始记录一下打到
0
0
0 以下最少要打多少次,然后算出来如果次数少于这个,你就根据通解,找到第一个大于他的就可以了。
代码
#include<set>
#include<cstdio>
#include<iostream>
#define ll __int128//用 long long 搞半天都会爆,然后懒的搞直接上 __int128
using namespace std;
ll T, n, m, x, y, GCD, z, v;
multiset <ll> nf;
ll a[100001], p[100001], kn[100001];
ll e[100001], maxn, ans, M;
ll read() {
ll re = 0, zf = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') zf = -zf;
c = getchar();
}
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re * zf;
}
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (!b) {
x = 1;
y = 0;
return a;
}
ll re = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return re;
}
void EXCRT() {
ans = 0;
M = 1;
for (ll i = 1; i <= n; i++) {
GCD = exgcd(M * e[i], p[i], x, y);
z = a[i] - e[i] * ans;//直接按着搞出来的公式搞
v = p[i] / GCD;
if (z % GCD) {
ans = -1;
return ;
}
x = (z / GCD * x) % v;
ans += M * x;
M *= v;
ans = (ans % M + M) % M;
}
//当前的答案打不死人,还要加到能打死人的最小
if (ans < maxn) ans += (maxn - ans + M - 1) / M * M;
return ;
}
void write(ll x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
int main() {
// freopen("read.txt", "r", stdin);
T = read();
while (T--) {
nf.clear();
n = read(); m = read();
for (ll i = 1; i <= n; i++) a[i] = read();
for (ll i = 1; i <= n; i++) p[i] = read();
for (ll i = 1; i <= n; i++) kn[i] = read();
for (ll i = 1; i <= m; i++) {
x = read();
nf.insert(x);
}
maxn = 0;//multset 求出每次用的武器
for (ll i = 1; i <= n; i++) {
multiset<ll>::iterator u;
if (a[i] < *nf.begin()) u = nf.begin();
else u = --nf.upper_bound(a[i]);
e[i] = *u;
nf.erase(u);
nf.insert(kn[i]);
maxn = max(maxn, (a[i] / e[i]) + (a[i] % e[i] != 0));
}
EXCRT();
write(ans);
putchar('\n');
}
return 0;
}