洛谷P4774 BZOJ5418 LOJ2721 [NOI2018]屠龙勇士(扩展中国剩余定理)
题目链接:
题目大意:这么长的题面,就饶了我吧emmm
这题第一眼看上去没法列出同余方程组。为什么?好像不知道用哪把剑杀哪条龙……
仔细一看,要按顺序杀龙,所以获得的剑出现的顺序也是固定的。
那么如果能把所有龙杀死,就能模拟出哪把剑杀那条龙了。
(以下设所有除 $n,m$ 外的数的最大值为 $v$)
$O(nm)$?
不,发现这里用剑的限制实际上是给出一个上界,来用lower_bound的。
插入也不要太暴力。我们想到什么?手写平衡树multiset!
这一部分复杂度是 $O(n\log m)$。
接下来就成了一个同余方程组:$atk_ix\equiv hp_i(\operatorname{mod}\ rec_i)$。
($atk_i$ 表示攻击第 $i$ 条龙的攻击力,$hp_i$ 表示第 $i$ 条龙的血量,$rec_i$ 表示第 $i$ 条龙的恢复能力)
但是好像没那么简单!有一些神奇的情况……要特判!
在考场上的话我肯定想不到特判任何东西
首先大家应该都知道,$x\equiv a_i(\operatorname{mod}\ b_i)$ 等价于 $x\equiv a_i\ \operatorname{mod}\ b_i(\operatorname{mod}\ b_i)$。
但是在这题中不行!你总不能把一头龙的血量凭空减少吧!
所以 $hp_i>rec_i$ 时(题目描述中的特性1不满足时)怎么办?
我们发现不满足特性1的点都满足 $rec_i=1$……也就是只要把他的血量打到小于0就赢了!
此时答案是 $\max\limits_{1\le i\le n}(\lceil\frac{hp_i}{atk_i}\rceil)$。
另外要注意 $x\ge\max(\lceil\dfrac{hp_i}{atk_i}\rceil)$。
至此所有特判结束。
我们一般的EXCRT同余方程组的形式是 $x\equiv a_i(\operatorname{mod}\ b_i)$。
但是这里有讨厌的 $atk_i$!更讨厌的是可能没有逆元,不能直接除过去!
我们来看看如何变形:
$atk_ix\equiv hp_i(\operatorname{mod}\ rec_i)$
$atk_ix+rec_iy=hp_i$
此时可以EXGCD解出 $x$ 的最小正整数解 $x_0$。
根据某定理(什么定理来着?)可得通解为 $x=x_0+\frac{rec_i}{\gcd(atk_i,rec_i)}k(k\in N^+)$。
于是就变成了 $x\equiv x_0(\operatorname{mod}\ \frac{rec_i}{\gcd{atk_i},rec_i})$。
这一部分复杂度为 $O(n\log v)$。
接下来就是裸的EXCRT了!
这一部分复杂度为 $O(n\log v)$。
等一下,还有一句提示没看到:
你所用到的中间结果可能很大,注意保存中间结果的变量类型。
突然慌张
冷静分析一下,我们在各种算法中模数都有可能超过int范围,要是一乘……
那……只能上龟速乘了。时间复杂度是没变,但是常数的话……
幸好不是wys的题,不然时限就是1s了
好吧,都说完了,上代码吧。(之前因为数据水没有注意 $x\ge\max(\lceil\dfrac{hp_i}{atk_i}\rceil)$)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int maxn=100010; 5 multiset<ll> mts; //模拟拿剑 6 int t,n,m; 7 bool spec,exist,same; 8 ll hp[maxn],rec[maxn],a[maxn],b[maxn]; //a,b表示最后方程组是x=a_i(mod\ b_i) 9 int award[maxn],atk[maxn]; //award是奖励的剑 10 ll qmul(ll a,ll b,ll mod){ //*速乘 11 ll ans=0; 12 for(;b;b>>=1,a=(a+a)%mod) if(b&1) ans=(ans+a)%mod; 13 return ans; 14 } 15 ll exgcd(ll a,ll b,ll &x,ll &y){ //模板 16 if(!b){x=1;y=0;return a;} 17 ll d=exgcd(b,a%b,y,x);y-=a/b*x;return d; 18 } 19 ll gcd(ll a,ll b){ //(不要问我为什么有这个) 20 if(!b) return a; 21 return gcd(b,a%b); 22 } 23 int main(){ 24 scanf("%d",&t); 25 while(t--){ 26 spec=false;same=exist=true; //spec是rec=1,same是hp=rec,exist是有没有解 27 mts.clear(); 28 scanf("%d%d",&n,&m); 29 for(int i=1;i<=n;i++) scanf("%lld",hp+i); 30 for(int i=1;i<=n;i++){scanf("%lld",rec+i);if(rec[i]<hp[i]) spec=true;} //rec<hp? 31 for(int i=1;i<=n;i++) scanf("%d",award+i); 32 for(int i=1;i<=m;i++){ 33 int x; 34 scanf("%d",&x); 35 mts.insert(x); //全部进去 36 } 37 for(int i=1;i<=n;i++){ 38 multiset<ll>::iterator it; 39 if(hp[i]<*mts.begin()) it=mts.begin(); //如果没有比血量小的剑,则用第一把(最小的) 40 else it=mts.upper_bound(hp[i]),it--; //否则用第一个大于它的-1(即最后一个小于等于它的) 41 atk[i]=*it; 42 mts.erase(it); 43 mts.insert(award[i]); //拿走再选 44 } 45 if(spec){ //特判hp>rec 46 ll ans=0; 47 for(int i=1;i<=n;i++) ans=max(ans,ll(ceil(1.0*hp[i]/atk[i]))); 48 printf("%lld\n",ans); 49 continue; 50 } 51 for(int i=1;i<=n;i++){ 52 if(!atk[i]){puts("-1");exist=false;break;} //无法攻击?无解 53 if(hp[i]!=rec[i]) same=false; //hp=rec? 54 ll x,y,d=exgcd(atk[i],rec[i],x,y); //解x0 55 if(hp[i]%d){puts("-1");exist=false;break;} //无解 56 x=(x+rec[i])%rec[i];b[i]=rec[i]/d;a[i]=qmul(x,hp[i]/d,b[i]); //真的x0推出b=rec/gcd(atk,rec),a=x0 57 } 58 if(same){ //特判hp=rec 59 ll ans=ll(ceil(1.0*hp[1]/atk[1])); 60 for(int i=2;i<=n;i++){ 61 ll x=ceil(1.0*hp[i]/atk[i]),d=gcd(ans,x); 62 ans=ans/d*x; 63 } 64 printf("%lld\n",ans);continue; 65 } 66 if(!exist) continue; 67 ll ans=a[1],mod=b[1]; //开始EXCRT(以下模板,不解释,可以去我的另一个博客看) 68 for(int i=2;i<=n;i++){ 69 ll x,y,d=exgcd(mod,b[i],x,y),r=((a[i]-ans)%b[i]+b[i])%b[i],tmp=mod/d*b[i]; 70 if(r%d){puts("-1");exist=false;break;} 71 x=(x+b[i])%b[i];x=qmul(x,r/d,b[i]); 72 ans=(qmul(mod,x,tmp)+ans)%tmp; 73 mod=tmp; 74 } 75 if(!exist) continue; 76 printf("%lld\n",ans); //终于没了 77 } 78 }
终于啊……