Label
EXCRT的简单变式(解标准线性同余方程组)+multiset实现普通平衡树功能
Description
https://www.luogu.com.cn/problem/P4774
Solution
首先,由于小D按1∼n1\sim n1∼n顺序屠龙,故我们用一个multiset动态维护SSS(SSS:剑的攻击力),根据题意,面对第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}\rceilx≥maxi=1n⌈siai⌉,使如下标准线性同余方程组成立:
{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}⎩⎪⎪⎪⎨⎪⎪⎪⎧s1x≡a1(modp1)s2x≡a2(modp2)......snx≡an(modpn)
(特判:由于我们必须把每一条龙的血量变成非正数后龙才会回血,故有上面xxx的取值范围)
虽然相比于EXCRTEXCRTEXCRT的标准形式,每一个线性同余方程前多了一个系数sis_isi,但类比EXCRTEXCRTEXCRT的推导过程,我们仍不难得出解该方程组的一般方法:
设前i−1i-1i−1个方程的通解为x0+tPi−1x_0+tP_{i-1}x0+tPi−1(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(pi−1,si−1)pi−1))(此处注意:根据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)}pi∣gcd(si,pi)sipi,故此处通解形式与EXCRTEXCRTEXCRT不同)则:对于第iii个同余方程six≡ai(modpi)s_ix\equiv a_i(modp_i)six≡ai(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+tPi−1)≡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+sitPi−1≡ai(modpi)
siPi−1t+six0=piu+ais_iP_{i-1}t+s_ix_0= p_iu+a_isiPi−1t+six0=piu+ai
siPi−1t+piu=ai−six0s_iP_{i-1}t+p_iu=a_i-s_ix_0siPi−1t+piu=ai−six0
据Bézout定理,若gcd(siPi−1,pi)∤ai−six0gcd(s_iP_{i-1},p_i)\nmid a_i-s_ix_0gcd(siPi−1,pi)∤ai−six0,则原方程组无解,反之即用EXCGD解出上述方程的一组特解t0,u0t_0,u_0t0,u0,ttt的通解即可表示为t=t0+kpigcd(siPi−1,pi)t=t_0+k\frac{p_i}{gcd(s_iP_{i-1},p_i)}t=t0+kgcd(siPi−1,pi)pi。
求出ttt后,前iii个方程组的解ansi=x0+tPi−1+kPians_i=x_0+tP_{i-1}+kP_iansi=x0+tPi−1+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;
}