题解:P16132 [ICPC 2018 NAIPC] Probe Droids
lailai0916 · · 题解
题意简述
炮台位于网格左下角,初始朝右。若当前方向还有机器人,就继续击毁最近的机器人;否则逆时针旋转到下一个有机器人的方向。
给定若干排名,求对应被击毁机器人的行列坐标。
解题思路
把炮台平移到原点。记
击毁顺序就是按斜率
水平轴上的
先解决排名统计。对于正有理数
前
求和部分使用类欧几里得算法。定义:
于是
有了单调的计数函数,可以二分第
采用固定分母
由于
本题中
这里不需要再限制近似分数的分子。真正的目标分数已经满足分子不超过
用连分数求这个最佳近似。维护相邻两项渐近分数
当下一项分母将超过
此时仅需比较
两个交叉乘积之差为
代码中的 approx 实现这一过程。所有距离比较都通过交叉相乘完成,不使用浮点数。连分数相邻项的分子和分母互质,所以最终返回的
恢复斜率后,这条射线上的机器人依次为
严格小于该斜率的内部点数是
代码先处理两条坐标轴,因而后续的 long long,类欧几里得中间乘积和近似距离比较使用 __int128。
每次询问至多进行
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using i128=__int128;
const ll K=1LL<<42;
ll n,m;
ll floor_sum(ll n,ll m,ll a,ll b)
{
ll res=0;
while(1)
{
res+=(i128)(a/m)*n*(n-1)/2+(i128)(b/m)*n;
a%=m;
b%=m;
i128 y=(i128)a*n+b;
if(y<m)break;
n=y/m;
b=y%m;
swap(a,m);
}
return res;
}
ll count(ll a,ll b)
{
if(!a)return 0;
ll t=min(m,(ll)((i128)n*b/a));
return n*(m-t)+floor_sum(t,b,a,a);
}
pair<ll,ll> approx(ll a,ll b,ll m)
{
ll p=0,q=1,r=1,s=0,x=a,y=b;
while(y)
{
ll k=x/y;
if(q+k*s>m)break;
ll u=p+k*r,v=q+k*s;
p=r;
q=s;
r=u;
s=v;
ll tmp=x%y;
x=y;
y=tmp;
}
if(!y)return {r,s};
ll k=(m-q)/s;
ll u=p+k*r,v=q+k*s;
i128 l=(i128)a*v-(i128)b*u,h=(i128)a*s-(i128)b*r;
if(l<0)l=-l;
if(h<0)h=-h;
return l*s<=h*v?pair<ll,ll>{u,v}:pair<ll,ll>{r,s};
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin>>n>>m>>q;
n--;
m--;
while(q--)
{
ll k;
cin>>k;
if(k<=m)
{
cout<<1<<' '<<k+1<<'\n';
continue;
}
k-=m;
if(k>n*m)
{
cout<<k-n*m+1<<' '<<1<<'\n';
continue;
}
ll l=0,r=n*K+1;
while(l<r)
{
ll mid=l+(r-l)/2;
if(count(mid,K)>=k)r=mid;
else l=mid+1;
}
auto [a,b]=approx(l,K,m);
ll pos=k-count(a,b)+min(n/a,m/b);
cout<<a*pos+1<<' '<<b*pos+1<<'\n';
}
return 0;
}