参考资料
题意简述
对非负整数三元组 定义一次操作:
给定 与 ,求把 变成 的最少操作次数,无法做到输出 。共 组,,。
解题思路
记 。注意 ,上式可改写为:
四种情形结果之和都是 ,故 是不变量; 之和不等即输出 。 时只有 ,答案为 。
在模 下观察:前三种情形恰是逐坐标乘 。以 为例,,且此时 , 已落在 内;故 。第四种情形是逐坐标乘 :。合起来,一次操作把三元组模 乘以 或 。
反过来,结果由「和恰为 」唯一确定。设三坐标模 的余数为 ,其和 ,即属于 :和为 时取 (对应乘 ),和为 时取 (对应乘 ),和为 时是某个顶点。于是施加 次操作的结果 ,就是与 或 同余、且和为 的唯一非负三元组——操作是确定的,从 出发只有一条轨迹。答案即最小的 使 ,否则 。
据此:
两式都对所有 成立,且组内三坐标取同一符号。
预处理
若 为偶:任意一次操作后三坐标全偶( 与 都是偶数)。又当三坐标全偶时「整组折半」与操作可交换,,于是 (在模 意义下)。所以走 步要求 全偶,且问题降为把 变到 。反复折半直到 为奇,并累加偏移量。若某步 仍偶而 不全偶,则无法再前进,就此停止。
求解
为奇时 模 可逆。三条同余式中,第三条由前两条与「和 」自动成立(),只需:
用 大步小步算法(Baby-Step Giant-Step,BSGS)求最小 。设 ,把 写作 ():小步枚举 ,把 连同 存入哈希表;大步枚举 ,查 。两坐标各在 内,打包成单个键 即可(不超过 ,long long 可容)。 自小到大、相同键保留较大的 ,首个命中即最小 。
取 总是有效;取 仅当 三坐标全正——否则该符号对应的态和为 而非 ,并不等于 (含 坐标的 只能由乘 到达)。两者取较小。
顶点(两坐标为 、第三坐标为 )是不动点:非顶点到不了顶点,顶点也只停在自身,单独判掉。
每组数据折半 、BSGS 。时间复杂度为 。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using tri=array<ll,3>;
const ll inf=0x3f3f3f3f3f3f3f3f;
tri F(tri a)
{
ll s=a[0]+a[1]+a[2];
if(2*a[0]>s)return {2*a[0]-s,2*a[1],2*a[2]};
if(2*a[1]>s)return {2*a[0],2*a[1]-s,2*a[2]};
if(2*a[2]>s)return {2*a[0],2*a[1],2*a[2]-s};
return {s-2*a[0],s-2*a[1],s-2*a[2]};
}
ll Pow(ll x,ll y,ll mod)
{
ll res=1;
x%=mod;
while(y)
{
if(y&1)res=(__int128)res*x%mod;
x=(__int128)x*x%mod;
y>>=1;
}
return res;
}
ll bsgs(ll a,ll b,ll c,ll d,ll s)
{
a%=s;b%=s;c%=s;d%=s;
if(a==c&&b==d)return 0;
ll m=sqrt(s)+1;
unordered_map<ll,ll> mp;
for(ll j=0;j<m;j++)
{
mp[c*s+d]=j;
c=c*2%s;d=d*2%s;
}
ll t=Pow(2,m,s);
for(ll i=1;i<=m;i++)
{
a=(__int128)a*t%s;b=(__int128)b*t%s;
if(mp.count(a*s+b))return i*m-mp[a*s+b];
}
return inf;
}
bool vtx(tri a)
{
return (a[0]==0)+(a[1]==0)+(a[2]==0)>=2;
}
ll core(tri a,tri b,ll s)
{
if(a==b)return 0;
if(vtx(a)||vtx(b))return inf;
ll res=bsgs(a[0],a[1],b[0],b[1],s);
if(b[0]&&b[1]&&b[2])res=min(res,bsgs(a[0],a[1],(s-b[0])%s,(s-b[1])%s,s));
return res;
}
ll solve(tri a,tri b)
{
if(a[0]+a[1]+a[2]!=b[0]+b[1]+b[2])return -1;
ll s=a[0]+a[1]+a[2];
if(!s)return 0;
ll ans=inf,cnt=0;
while(1)
{
if(a==b)ans=min(ans,cnt);
if(s&1)
{
ll c=core(a,b,s);
if(c<inf)ans=min(ans,cnt+c);
break;
}
if((b[0]|b[1]|b[2])&1)break;
a=F(a);
a={a[0]/2,a[1]/2,a[2]/2};
b={b[0]/2,b[1]/2,b[2]/2};
s/=2;cnt++;
}
return ans<inf?ans:-1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--)
{
tri a,b;
cin>>a[0]>>a[1]>>a[2]>>b[0]>>b[1]>>b[2];
cout<<solve(a,b)<<'\n';
}
return 0;
}