P4774[NOI2018]屠龙勇士【EXCRT】

正题

题目链接:https://www.luogu.com.cn/problem/P4774


题目大意

\(n\)个龙血量为\(a_i\),回复能力为\(p_i\),死亡后掉落剑的攻击力\(t_i\)\(m\)把剑,攻击力为\(b_i\)

\(1\)开始打,每次使用不大于当前龙血量的剑中攻击力最低的一把(没有就用攻击力最低的),造成\(x\times atk\)点伤害,然后当前的剑坏掉。

求一个最小的\(x\)使得所有龙被攻击后血量是\(p_i\)的倍数。

\(1\leq n,m\leq 10^5\),满足\(p=1\)或者\(a_i\leq p_i\),所有\(p_i\)的公倍数不超过\(10^{12}\)


解题思路

额,先用\(set\)处理出每个龙用哪把剑打\(c_i\),然后就是对于每条龙的条件就是

\[c_ix\equiv a_i(mod\ p_i),c_ix\geq a_i \]

后面那个条件可以去掉,我们先求出满足所有\(c_ix\geq a_i\)的最小\(x\),后面再调整。

然后前面那个东西可以用\(EXCRT\)搞了,假设我们上一次求到的答案为\(ans\),目前\(p_i\)的公倍数是\(M\),那么现在的通解就是\(ans+Mx\)。我们需要求出一个\(x\)满足

\[c_i(ans+Mx)\equiv a_i(mod\ p_i) \]

\[\Rightarrow Mx+p_iy=a_i-c_i\times ans \]

然后就可以扩欧合并了。


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<set>
#define ll __int128
using namespace std;
ll read(){
	ll x=0,f=1; char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
	return x*f;
}
void print(ll x)
{if(x>9)print(x/10);putchar(x%10+48);return;}
const ll N=1e5+10;
multiset<ll> s;
ll n,m,T,a[N],p[N],t[N],c[N];
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);
	ll z=x;x=y;y=z-a/b*y;
	return d;
}
void work(){
	n=read();m=read();s.clear();
	ll mx=0;
	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++)t[i]=read();
	for(ll i=1;i<=m;i++){
		ll x=read();
		s.insert(x);
	}
	for(ll i=1;i<=n;i++){
		multiset<ll>::iterator it;
		if(a[i]<*s.begin())it=s.begin();
		else it=--s.upper_bound(a[i]);
		c[i]=*it;s.erase(it);s.insert(t[i]);
		mx=max(mx,(a[i]-1)/c[i]+1);
	}
	ll M=1,x,y,ans=0;
	for(ll i=1;i<=n;i++){
		ll d=exgcd(M*c[i],p[i],x,y);
		ll w=a[i]-c[i]*ans,v=p[i]/d;
		if(w%d){puts("-1");return;}
		x=w/d*x%v;ans=ans+x*M;
		M=M*v;ans=(ans%M+M)%M;
	}
	if(ans<mx)ans+=(mx-ans+M-1)/M*M;
	print(ans);putchar('\n');
}
signed main()
{
	scanf("%lld",&T);
	while(T--){work();}
	return 0;
}
原文地址:https://www.cnblogs.com/QuantAsk/p/14430864.html