【NOIP模拟】丢你拉姆
题面


分析
太困难了TAT,暴搜的20分都没搜对。
其实原题是跳跳棋,只能说有些人py能力太强,题面都改成这样都能找出来。。
我们考虑一个有序状态 S(x ,y, z),x<=y<=z。观察他的转移方案:
设 l = y-x, r = z-y。
1. 中间向两边跳 S(x, y, z) - S(x-l, y-l, z), S(x, y, z) - S(x, y-z, z-r)
2. 两边向中间,
𝐥 < 𝐫, 𝐒(𝐲,𝐳,𝐴) – 𝐒(𝐲 + 𝐥,𝐳 + 𝐥,𝐴)
𝐥 > 𝐫, 𝐒(𝐲,𝐳,𝐴) – 𝐒(𝐲,𝐳 − 𝐫,𝐴 − 𝐫)
由于转移时可逆的。我们将一个状态两边向中间转移后的状态设为这个状态的父亲,我们发现,这刚好形成了一颗树!
然后问题就变成了询问两点间的距离。由于得不出树的形态,我们可以先让其中一个节点先跳到根另一个节点在一个个往上跳,看有没有交集,如果到根都没有交集就是无解
优化:
我们可以用倍增的思想来实现向上跳的操作。
由于树的深度可能会很大,所以我们无法把倍增的数组求出来,需要动
态访问倍增数组。我们可以令二元组(a, b)代替状态 S,a=y-x, b=z-y 且 a>b。
那么显然状态 S 的父亲 SS(xx, yy, zz)满足 yy-xx=a-b, zz-yy=b,也就是说一个状态向上跳二元组(a, b)就会变成(a-b, b),这像什么?更相减损术!那么就可以用辗转相除的思想加速到 O(Logb)
INF不能取大了,不然就等着着莫名WA几发吧
代码
#include<bits/stdc++.h> using namespace std; #define N 5 #define INF 0x3f3f3f3f int d1,d2,d3,rets,retd,tmp,ans; struct email { int x[N]; void read(){for(int i=1;i<=3;i++)scanf("%d",&x[i]);} bool operator !=(const email &a)const {return x[1]!=a.x[1]||x[2]!=a.x[2]||x[2]!=a.x[2];} }st,ed; email cal(const email &a,int k,int &ret) { email ans=a; int dis1=a.x[2]-a.x[1],dis2=a.x[3]-a.x[2]; if(dis1==dis2)return ans; else if(dis1<dis2) { int t=min(k,(dis2-1)/dis1); k-=t;ret+=t; ans.x[2]+=t*dis1,ans.x[1]+=t*dis1; } else { int t=min(k,(dis1-1)/dis2); k-=t;ret+=t; ans.x[2]-=t*dis2,ans.x[3]-=t*dis2; } if(k)return cal(ans,k,ret); else return ans; } int main() { st.read();ed.read(); sort(st.x+1,st.x+4);sort(ed.x+1,ed.x+4); email rs=cal(st,INF,rets),rd=cal(ed,INF,retd); if(rs!=rd){printf("NO\n");return 0;} printf("YES\n"); if(rets>retd)swap(rets,retd),swap(st,ed); ans=retd-rets; ed=cal(ed,ans,tmp); int l=0,r=rets,mid; while(l<=r) { mid=l+r>>1; if(cal(st,mid,tmp)!=cal(ed,mid,tmp))l=mid+1; else r=mid-1; } printf("%d\n",ans+2*l); return 0; }
“Make my parents proud,and impress the girl I like.”

浙公网安备 33010602011771号