P4774 [NOI2018] 屠龙勇士

博客详细介绍了如何利用扩展中国剩余定理(EXCRT)解决带有系数的标准线性同余方程组,并结合multiset实现动态维护剑的攻击力,以解决NOI竞赛中的一道题目。文章通过数学推导和代码实现,展示了求解过程和注意事项,包括特判、精度处理和慢速乘法的应用。

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

Label

EXCRT的简单变式(解标准线性同余方程组)+multiset实现普通平衡树功能

Description

https://www.luogu.com.cn/problem/P4774

Solution

首先,由于小D按1∼n1\sim n1n顺序屠龙,故我们用一个multiset动态维护SSSSSS:剑的攻击力),根据题意,面对第iii条巨龙时,我们查询可重集内aia_iai的前驱并作为SiS_iSi(屠第iii条龙所用剑的攻击力),若aia_iai无前驱则令Si=aiS_i=a_iSi=ai的后继。

当我们求出所有SiS_iSi后,将题意转化为数学语言即为:求最小的x≥maxi=1n⌈aisi⌉x\ge max_{i=1}^{n}\lceil\frac{a_i}{s_i}\rceilxmaxi=1nsiai,使如下标准线性同余方程组成立:
{s1x≡a1(modp1)s2x≡a2(modp2)......snx≡an(modpn)\begin{cases}s_1x\equiv a_1(modp_1)\\s_2x\equiv a_2(modp_2)\\......\\s_nx\equiv a_n(modp_n)\end{cases}s1xa1(modp1)s2xa2(modp2)......snxan(modpn)

(特判:由于我们必须把每一条龙的血量变成非正数后龙才会回血,故有上面xxx的取值范围)

虽然相比于EXCRTEXCRTEXCRT的标准形式,每一个线性同余方程前多了一个系数sis_isi,但类比EXCRTEXCRTEXCRT的推导过程,我们仍不难得出解该方程组的一般方法:

设前i−1i-1i1个方程的通解为x0+tPi−1x_0+tP_{i-1}x0+tPi1(P=lcm(p1gcd(p1,s1),p2gcd(p2,s2)...pi−1gcd(pi−1,si−1)))(P=lcm(\frac{p_1}{gcd(p_1,s_1)},\frac{p_2}{gcd(p2,s_2)}...\frac{p_{i-1}}{gcd(p_{i-1},s_{i-1})}))(P=lcm(gcd(p1,s1)p1,gcd(p2,s2)p2...gcd(pi1,si1)pi1))(此处注意:根据EXCRTEXCRTEXCRT内通解的推导及gcd(si,pi)∣sigcd(s_i,p_i)|s_igcd(si,pi)si,我们可得pi∣sipigcd(si,pi)p_i|\frac{s_ip_i}{gcd(s_i,p_i)}pigcd(si,pi)sipi,故此处通解形式与EXCRTEXCRTEXCRT不同)则:对于第iii个同余方程six≡ai(modpi)s_ix\equiv a_i(modp_i)sixai(modpi)∃t[si(x0+tPi−1)≡ai(modpi)]\exist t[s_i(x_0+tP_{i-1})\equiv a_i(modp_i)]t[si(x0+tPi1)ai(modpi)]即说明前iii个同余方程组有解,反之则无解。

si(x0+tP)≡ai(modpi)s_i(x_0+tP)\equiv a_i(modp_i)si(x0+tP)ai(modpi)化成二元一次不定方程的形式:

six0+sitPi−1≡ai(modpi)s_ix_0+s_itP_{i-1}\equiv a_i(modp_i)six0+sitPi1ai(modpi)

siPi−1t+six0=piu+ais_iP_{i-1}t+s_ix_0= p_iu+a_isiPi1t+six0=piu+ai

siPi−1t+piu=ai−six0s_iP_{i-1}t+p_iu=a_i-s_ix_0siPi1t+piu=aisix0

据Bézout定理,若gcd(siPi−1,pi)∤ai−six0gcd(s_iP_{i-1},p_i)\nmid a_i-s_ix_0gcd(siPi1,pi)aisix0,则原方程组无解,反之即用EXCGD解出上述方程的一组特解t0,u0t_0,u_0t0,u0ttt的通解即可表示为t=t0+kpigcd(siPi−1,pi)t=t_0+k\frac{p_i}{gcd(s_iP_{i-1},p_i)}t=t0+kgcd(siPi1,pi)pi

求出ttt后,前iii个方程组的解ansi=x0+tPi−1+kPians_i=x_0+tP_{i-1}+kP_iansi=x0+tPi1+kPi。将此过程归纳即为求方程组解的一般过程。

最后,注意面对第111条龙时诸变量的取值。

总结:1、注重每一步推导过程的准确性;

​ 2、注意特判情况; 3、留意炸精度的地方并适当使用慢速乘。

Code

毕竟此题是近几年NOI的D2T1,故需要注意的细节不在少,详见如下代码。

#include<cstdio>
#include<iostream>
#include<set>
#define ll long long
using namespace std;

const int MAXN=2e5;
int T,N,M;
ll ai[MAXN],pi[MAXN],fsts[MAXN],gives,si[MAXN];
ll atktot,maxatk,atkstep,A,B,C,ans,P,Pi,G,x,y,xi,yi;
multiset<ll>S;

ll Max(ll a,ll b) { return ((a>b)?(a):b); }
ll Gcd(ll a,ll b) { return ((b==0)?a:Gcd(b,a%b)); }
ll Lcm(ll a,ll b) { return (a/Gcd(a,b))*b; }
ll Msc(ll a,ll b,ll MOD)
{
	ll tot=0;
	for(;b;b>>=1,a=(a+a)%MOD)
		if(b&1)	tot=(tot+a)%MOD;
	return tot;
}

void Exgcd(ll a,ll b)
{
	if(b==0) { x=1,y=0; return; }
	Exgcd(b,a%b);
	xi=x,yi=y;
	x=yi,y=xi-(a/b)*yi;
}

ll Excrt()
{
	ans=0LL; P=1LL; maxatk=0LL;
	for(int i=1;i<=N;++i)
	{
		atktot=ai[i]/si[i];
		if(ai[i]%si[i]>0)	++atktot;
		maxatk=Max(maxatk,atktot);//此三句:判断至少需攻击maxatk次(因为最后线性同余方程的解有可能小于maxatk,不合法) 
		A=si[i]*P,B=pi[i],C=ai[i]-si[i]*ans;
		G=Gcd(A,B);
		if(G<0)	G=-G;//便于使用第一次慢速乘 
		if(C%G!=0)	return -1;
		Exgcd(A,B);
		x=(x%(B/G)+(B/G))%(B/G);//将x变为正数便于Msc 
		x=(Msc(C/G,x,(B/G))+(B/G))%(B/G);//注意:ax+by=c通解形式下步长与ax+by=g一致!(此处会爆longlong,需龟速乘) 
		if(x==0)	x=B/G;//x不能为0 
		Pi=Lcm(P,B/Gcd(B,si[i]));//注意:通解不再是X0+kLcm(p1,p2...pn) 
		if(i==1)	ans=x;
		else	ans=(ans+Msc(P,x,Pi))%Pi;
		P=Pi;
		ans=(ans%P+P)%P;
		if(ans==0)	ans=P;//ansx不能为0且不能小于将每条龙砍成负血的最小刀数
	}
	if(ans<maxatk)//特判 
	{
		atkstep=(maxatk-ans)/P;
		ans+=atkstep*P;
		if((maxatk-ans)%P>0)	ans+=P;
	}
	return ans;
}

void work()
{
	S.clear();
	scanf("%d%d",&N,&M);
	for(int i=1;i<=N;++i)	scanf("%lld",&ai[i]);
	for(int i=1;i<=N;++i)	scanf("%lld",&pi[i]);
	for(int i=1;i<=N;++i)	scanf("%lld",&fsts[i]);
	for(int i=1;i<=M;++i)
	{
		scanf("%lld",&gives);
		S.insert(gives);
	}
	for(int i=1;i<=N;++i)
	{
		if(S.upper_bound(ai[i])!=S.begin())	si[i]=*--S.upper_bound(ai[i]);
		else	si[i]=*S.upper_bound(ai[i]);
		S.erase(S.find(si[i]));
		S.insert(fsts[i]);
	}
	cout<<Excrt()<<'\n';
}

int main()
{
	scanf("%d",&T);
	for(int i=1;i<=T;++i)	work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值