题解:P11846 [USACO25FEB] Transforming Pairs P
Twilight_star · · 题解
题目链接
首先先讨论弱化版 P11841 [USACO25FEB] Transforming Pairs S。
弱化版保证
我们发现,
- 当
c>d 时,只能从(c-d,d) 转移过来。 - 当
c<d 时,只能从(c,d-c) 转移过来。 - 当
c=d 时,能从(c,0) 或(0,d) 转移过来,但是弱化版讨论范围在正整数,所以暂时不考虑。
这个前置状态很像用更相减损法求
每一次操作钦定
现在考虑本题。我们按
1. a,b 同号 (包含 a=0 或 b=0 )
此时如果
于是一定有
回顾弱化版,当
-
如果将
c 减为0 则最终
(c,d) 会成为(0,d) 。于是a=0 且b=d 则可以成功。 -
如果将
d 减为0 则一定是将
(c,d) 减为(d,d) 然后再一步减为(d,0) 。所以a=d 且b=0 时也是可以的。
代码如下:
int calc(int a,int b,int c,int d)
{//计算 a,b>=0 时的答案
if(c<0||d<0) return -1;
if(a==c&&b==d) return 0;
int res=0;
while(c!=0&&d!=0)
{
if(a==c&&b==d) return res;
if(c<d) swap(c,d),swap(a,b);
if(b==d&&c>=a&&c%d==a%d) return res+(c-a)/d;//能否把c删成a
if(c%d==0&&((a==0&&b==d)||(a==d&&b==0))) return res+c/d;//删成0是否满足要求
res+=c/d;
c%=d;
}
if(a==c&&b==d) return res;
return -1;
}
void solve()
{
a=read(),b=read(),c=read(),d=read();
if(a==c&&b==d) return printf("0\n"),void();
if(a<=0&&b<=0) a=-a,b=-b,c=-c,d=-d;
if(a<0) swap(a,b),swap(c,d);
if(a>=0&&b>=0)//a,b同号
{
if(c<0||d<0) return printf("-1\n"),void();
return printf("%lld\n",calc(a,b,c,d)),void();
}
}
2. a,b 异号,c,d 异号
我们会发现,在操作途中,当
于是当
剩下的情况仅剩
举个例子,现在
于是这种情况的答案为上种情况
//a,b异号,c,d异号,已保证a>0
if(c<0&&d>0) return printf("-1\n"),void();
if(c>=0&&d<=0) return printf("%lld\n",calc(c,-d,a,-b)),void();
3. a,b 异号,c,d 同号
现在需要考虑
我们把它搬到平面直角坐标系上讨论。存在两个坐标
其实什么时候让
如下图,蓝色边为跨过
我们会发现,对于一段连续的向左的一组转移,我们可以从中选择一个点向上转移,并且向上转移后的落点在一条斜率为
但是,有些落点是无法到达
设
于是枚举
然后对交点计算总的操作次数即可。
注意不要漏掉落点在
总时间复杂度
具体判断条件见代码:
//a,b异号,c,d同号
if(c<=0&&d<=0) a=-a,b=-b,c=-c,d=-d;
if(a<0) swap(a,b),swap(c,d);
vector< array<int,3> > q;
for(int x=c,y=d,num=0;x>0&&y>0;)
{
if(y>=x) num+=y/x,y%=x;
else q.push_back({y,x,num}),num+=x/y,x%=y;
}//预处理 (c,d) 倒推时到达每一个向左组的操作次数
int ans=inf,num=0;//num为(a,b)到当前组的
while(a>0&&b<0)
{
if(a+b==0)//向上走到坐标轴上
{
ans=min(ans,num+1+calc2(a,0,c,d)); //calc2(a,b,c,d): 当calc(a,b,c,d)==-1时为inf,否则为calc(a,b,c,d)
break;
}
if(a+b<0)//向上走,不用考虑穿过坐标轴
{
num+=(-b)/a;
b=-((-b)%a);
continue;
}
//向左走
for(auto i:q)//枚举 (c,d) 倒推途中向左转移的组
{
int y=i[0],mx=i[1];
if(y<=a+b&&(a-y)%(-b)==0)//(a,a+b)为本组最右的落点
{
int k=(a+b-y)/(-b);//本组第几个点
int x=a+k*b;//落点横坐标
if(x<=mx&&(mx-x)%y==0) ans=min(ans,num+i[2]+1+k+(mx-x)/y);//落点能在(c,d)路径上
}
}
if(a%(-b)==0) ans=min(ans,num+a/(-b)+calc2(-b,0,c,d));//向上拐,走到x轴上
num+=a/(-b);
a%=(-b);
}
if(a>=0&&b>=0) ans=min(ans,num+calc2(a,b,c,d));
if(ans>=inf) printf("-1\n");
else write(ans),putchar('\n');