Label
EXCRT的简单变式(解标准线性同余方程组)+multiset实现普通平衡树功能
Description
https://www.luogu.com.cn/problem/P4774
Solution
首先,由于小D按 1 ∼ n 1\sim n 1∼n顺序屠龙,故我们用一个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
x≥maxi=1n⌈siai⌉,使如下标准线性同余方程组成立:
{
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}
⎩⎪⎪⎪⎨⎪⎪⎪⎧s1x≡a1(modp1)s2x≡a2(modp2)......snx≡an(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 i−1个方程的通解为 x 0 + t P i − 1 x_0+tP_{i-1} x0+tPi−1 ( 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(pi−1,si−1)pi−1))(此处注意:根据 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)} pi∣gcd(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) six≡ai(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+tPi−1)≡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+sitPi−1≡ai(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 siPi−1t+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 siPi−1t+piu=ai−six0
据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(siPi−1,pi)∤ai−six0,则原方程组无解,反之即用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(siPi−1,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+tPi−1+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;
}