题解 Luogu P4774 [NOI2018]屠龙勇士
题意
有 \(n\) 只巨龙,第 \(i\) 只的初始生命值为 \(a_i\),并且拥有一个生命恢复值 \(p_i\)。你有 \(m\) 把剑,每把剑有一个攻击力。现在你要用这 \(m\) 把剑依次杀死这 \(n\) 只巨龙。
在攻击第 \(i\) 只巨龙之前,你需要选出攻击力不大于 \(a_i\) 且攻击力最大的一把剑。如果这样的剑不存在,则选出攻击力最小的一把剑。设这把剑的攻击力为 \(v\),接下来:
- 你会连续攻击巨龙 \(x\) 次,巨龙的生命值变为 \(a_i -x\times v\);
- 巨龙会不断恢复生命值,每次恢复 \(p_i\),直到生命值非负;
- 此时,若巨龙生命值为 \(0\),巨龙死去;
- 用来攻击的这把剑会消失。与此同时,如果巨龙死去,你会获得一把新的剑,攻击力可能和这一把不同。
求 \(x\) 的最小非负整数解,或者指出 \(x\) 不存在。
\(1 \leq n,m \leq 10^5,p_i \geq 1,\operatorname{lcm}(p_i) \leq 10^{12},a_i \leq 10^{12}\)
题解
观察到用来攻击每个巨龙的剑的攻击力是固定的。设攻击第 \(i\) 只巨龙时,用到的剑攻击力为 \(b_i\)。考虑列出方程组
考虑对方程进行化简,将形如 \(b_ix \equiv a_i \pmod{p_i}\) 的方程化为 \(b_ix + p_iy = a_i\) 的标准扩欧形式。
首先判断该方程是否有解。根据裴蜀定理,该方程有解的充要条件是 \(\gcd(b_i,p_i) \mid a_i\)。使用 exgcd 求出原方程的一组特解 \(x = x_0\),那么 \(x\) 的解集为 \(x = x_0 + k \dfrac{p_i}{\gcd(b_i,p_i)}\)。再化回同余方程的形式,得 \(x \equiv x_0 \pmod{\dfrac{p_i}{\gcd(b_i,p_i)}}\)。
对于每个方程进行上述处理,便得到了可以使用 exCRT 的形式。设该方程组的最小非负整数解为 \(s\)。注意到 \(s\) 不一定是原问题的一个合法解,因为 \(s\) 次攻击可能不一定会使某条巨龙的生命值变为非正数。这个时候就需要将 \(s\) 不断增加 \(\operatorname{lcm}(\dfrac{p_i}{\gcd(b_i,p_i)})\)(即新方程组模数的最小公倍数),直到 \(s\) 次攻击可以把所有巨龙的生命值变为非正数。
# include <bits/stdc++.h>
# define int __int128
const int N=100010,INF=0x3f3f3f3f;
int n,m;
int a[N],p[N],b[N],c[N];
std::multiset <int> S;
int pval[N],bval[N];
int maxx;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
void print(int x){
if(x<0)
putchar('-'),x=-x;
if(x>9)
print(x/10);
putchar(x%10+'0');
return;
}
inline int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;
}
int gcd=exgcd(b,a%b,x,y),Temp;
Temp=x,x=y,y=Temp-(a/b)*y;
return gcd;
}
inline bool build_excrt(void){
for(int i=1;i<=n;++i){
int A=c[i],B=p[i],C=a[i],xz,yz;
int gcd=exgcd(A,B,xz,yz);
if(C%gcd)
return false;
bval[i]=xz*C/gcd,pval[i]=B/gcd,bval[i]%=pval[i];
}
return true;
}
inline int solve(void){
if(!build_excrt()){
return -1;
}
int M=pval[1],ans=bval[1];
for(int i=2;i<=n;++i){
int A=M,B=pval[i],C=((bval[i]-ans)%pval[i]+pval[i])%pval[i],x,y,gcd;
gcd=exgcd(A,B,x,y);
if(C%gcd)
return -1;
ans+=C/gcd*x*M;
M=M/gcd*pval[i];
ans=(ans%M+M)%M;
}
if(ans<maxx)
ans+=((maxx-ans-1)/M+1)*M;
return ans;
}
signed main(void){
int T=read();
while(T--){
maxx=0;
S.clear(),n=read(),m=read();
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=n;++i)
p[i]=read();
for(int i=1;i<=n;++i)
b[i]=read();
while(m--)
S.insert(read());
for(int i=1;i<=n;++i){
std::multiset <int>::iterator it=S.upper_bound(a[i]);
if(S.begin()!=it)
--it;
c[i]=*it,S.erase(it),S.insert(b[i]);
maxx=std::max(maxx,(a[i]-1)/c[i]+1);
}
print(solve()),puts("");
}
return 0;
}