P4774 [NOI2018] 屠龙勇士

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

Label

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

Description

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

Solution

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

当我们求出所有 S i S_i Si后,将题意转化为数学语言即为:求最小的 x ≥ m a x i = 1 n ⌈ a i s i ⌉ x\ge max_{i=1}^{n}\lceil\frac{a_i}{s_i}\rceil xmaxi=1nsiai,使如下标准线性同余方程组成立:
{ s 1 x ≡ a 1 ( m o d p 1 ) s 2 x ≡ a 2 ( m o d p 2 ) . . . . . . s n x ≡ a n ( m o d p n ) \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)

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

虽然相比于 E X C R T EXCRT EXCRT的标准形式,每一个线性同余方程前多了一个系数 s i s_i si,但类比 E X C R T EXCRT EXCRT的推导过程,我们仍不难得出解该方程组的一般方法:

设前 i − 1 i-1 i1个方程的通解为 x 0 + t P i − 1 x_0+tP_{i-1} x0+tPi1 ( P = l c m ( p 1 g c d ( p 1 , s 1 ) , p 2 g c d ( p 2 , s 2 ) . . . p i − 1 g c d ( p i − 1 , s i − 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))(此处注意:根据 E X C R T EXCRT EXCRT内通解的推导及 g c d ( s i , p i ) ∣ s i gcd(s_i,p_i)|s_i gcd(si,pi)si,我们可得 p i ∣ s i p i g c d ( s i , p i ) p_i|\frac{s_ip_i}{gcd(s_i,p_i)} pigcd(si,pi)sipi,故此处通解形式与 E X C R T EXCRT EXCRT不同)则:对于第 i i i个同余方程 s i x ≡ a i ( m o d p i ) s_ix\equiv a_i(modp_i) sixai(modpi) ∃ t [ s i ( x 0 + t P i − 1 ) ≡ a i ( m o d p i ) ] \exist t[s_i(x_0+tP_{i-1})\equiv a_i(modp_i)] t[si(x0+tPi1)ai(modpi)]即说明前 i i i个同余方程组有解,反之则无解。

s i ( x 0 + t P ) ≡ a i ( m o d p i ) s_i(x_0+tP)\equiv a_i(modp_i) si(x0+tP)ai(modpi)化成二元一次不定方程的形式:

s i x 0 + s i t P i − 1 ≡ a i ( m o d p i ) s_ix_0+s_itP_{i-1}\equiv a_i(modp_i) six0+sitPi1ai(modpi)

s i P i − 1 t + s i x 0 = p i u + a i s_iP_{i-1}t+s_ix_0= p_iu+a_i siPi1t+six0=piu+ai

s i P i − 1 t + p i u = a i − s i x 0 s_iP_{i-1}t+p_iu=a_i-s_ix_0 siPi1t+piu=aisix0

据Bézout定理,若 g c d ( s i P i − 1 , p i ) ∤ a i − s i x 0 gcd(s_iP_{i-1},p_i)\nmid a_i-s_ix_0 gcd(siPi1,pi)aisix0,则原方程组无解,反之即用EXCGD解出上述方程的一组特解 t 0 , u 0 t_0,u_0 t0,u0 t t t的通解即可表示为 t = t 0 + k p i g c d ( s i P i − 1 , p i ) t=t_0+k\frac{p_i}{gcd(s_iP_{i-1},p_i)} t=t0+kgcd(siPi1,pi)pi

求出 t t t后,前 i i i个方程组的解 a n s i = x 0 + t P i − 1 + k P i ans_i=x_0+tP_{i-1}+kP_i ansi=x0+tPi1+kPi。将此过程归纳即为求方程组解的一般过程。

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

总结: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、付费专栏及课程。

余额充值