【题解】Probe Droids - The North American Invitational Programming Contest 2018 F

传送门


【大意】

在一个 \(n\times m\) 的矩阵内,你处于左下角 \((1,1)\) 处,其他整点上均有一个敌人。

你有一个初始水平向右的炮台,每次攻击可以消灭一个敌人。当这一条线的敌人都消灭过后,炮台逆时针旋转,直到这条线上再次有敌人。依此类推,直到消灭所有敌人。

\(q\) 次询问,每次询问数字 \(x\) ,询问第 \(x\) 个消灭的敌人的坐标。


【分析】

忽略炮台同一列、同一行的敌人。炮台攻击点 \((a,b)\) 的直线为 \(y={a-1\over b-1}x\) 。该直线上的敌人共有的特性是:斜率的最简分数均满足分子为 \({a-1\over \gcd(a-1, b-1)}\) ,分母为 \({b-1\over \gcd(a-1, b-1)}\)

很显然可以使用 Stern-Brocot 树的性质。建立一棵分子不超过 \((n-1)\) ,分母不超过 \((m-1)\) 的 SB 树,每个点维护这条直线上的敌人个数。

当最简分数为 \({a\over b}\) 时,直线上的敌人个数即为 \(\min\{(n-1)/a, (m-1)/b\}\)

由于 SB 树已经满足二叉搜索树的性质,可以直接统计小于等于该值的数字个数。然后在 SB 树上二分结果即可。

但由于分数 \({0\over 1}\)\({1\over 0}\) 在 SB 树上不存在,需要特殊处理:

当询问的 \(x\leq m-1\) 时,结果即为 \((1, x+1)\)

当询问的 \(x> nm-m\) 时,结果即为 \((pos-nm+n+1, 1)\)

否则,相当于在一个 \([1, n-1]\times [1,m-1]\) 的整点阵对应的 SB 树上询问第 \(x'=x-m+1\) 个数。

当询问的点处于最简分数 \({a\over b}\) 上时,若小于该数的整点个数为 \(y\) ,则第 \(x\) 个被消灭的敌人实际上为该直线上第 \((x'-y)\) 个敌人。

因此敌人实际坐标是 \([1,n-1]\times [1,m-1]\) 整点阵中的坐标 \(((x'-y)a, (x'-y)b)\)

即询问的答案为 \(((x'-y)a+1, (x'-y)b+1)\)


如果将 SB 树看成线段树(堆)的方式实现,能很幸运地收集到 RE 或者 MLE 。

如果将 SB 树使用二叉树(指针动态申请内存)的方式实现,能很幸运地获得 MLE 。

因此,我们实际求解过程中,根本无法存下 SB 树的具体信息。

思考一波,我们在 SB 树上需要维护的信息是小于等于分数 \({a\over b}\) 的分数个数。这个信息能不能动态求解呢?

实际上,满足该条件的整点对应到 \([1,n-1]\times [1,m-1]\) 的整点阵中,就是位于直线 \(y={a\over b}x\) 直线下方的整点数。这不就是类欧几里得算法吗?

考虑列出求和式子:

\(\begin{aligned} ans&=\sum_{i=1}^{m-1}\sum_{j=1}^{n-1}[j\leq {ia\over b}] \\\\&=\sum_{i=1}^{m-1}\min\{n-1, \lfloor{ia\over b}\rfloor\} \\\\&=\sum_{i=1}^{\min\{m-1,\lfloor {b(n-1)+b-1\over a}\rfloor\}}\lfloor{ia\over b}\rfloor+\sum_{i=\min\{m-1,\lfloor {b(n-1)+b-1\over a}\rfloor\}+1}^{m-1}(n-1) \\\\&=\sum_{i=1}^{\min\{m-1,\lfloor {bn-1\over a}\rfloor\}}\lfloor{ia\over b}\rfloor+(n-1)\cdot \max\{m-1-\min\{m-1,\lfloor {bn-1\over a}\rfloor\}, 0\} \\\\&=\sum_{i=1}^{lim} \lfloor{ia\over b}\rfloor+(n-1)\cdot \max\{m-1-lim,0\}&,lim:=\min\{m-1,\lfloor {bn-1\over a}\rfloor\} \end{aligned}\)

对于前半部分,直接类欧几里得算法求和;对于后半部分,直接 \(O(1)\) 求解。

因此,我们可以直接传入 \({a\over b}\) ,然后用类欧几里得算法 \(O(\log n)\) 求出小于等于该分数的分数个数。

再加上树上二分的复杂度,总复杂度即为 \(O(q\log^2 n)\)


【代码】

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define dd(x) cerr << #x <<" = "<< x <<" "
#define de(x) cerr << #x <<" = "<< x <<endl
int n, m, q;
inline ll likeEucilid(ll a,ll b,ll c,ll n){
    ll f=0;
    if(a>=c||b>=c){
        ll ans=likeEucilid(a%c,b%c,c,n);
        ll ac=a/c,bc=b/c,s0=(n+1),s1=n*(n+1)/2;
        f=(ac*s1+bc*s0+ans);
    }
    else if(a!=0){
        ll m=(a*n+b)/c;
        ll ans=likeEucilid(c,c-b-1,a,m-1);
        f=(n*m-ans);
    }
    return f;
}
inline ll calc(int a, int b) {//sum j=1->m sum i=1->n [i<=ja/b]
	int lim=min((ll)m-1, ((ll)b*n-1)/a);
	return likeEucilid(a, 0, b, lim)+(ll)(n-1)*max(m-1-lim, 0);
}
inline void query(int a, int b, int c, int d, ll pos, int &x, int &y) {
	int nowa=a+c, nowb=b+d;
	ll tot=calc(nowa, nowb);
	if(pos>tot) return query(nowa, nowb, c, d, pos, x, y), void();
	int cnt=min((n-1)/nowa, (m-1)/nowb);
	if(pos>tot-cnt) return pos-=tot-cnt, x=pos*nowa, y=pos*nowb, void();
	query(a, b, nowa, nowb, pos, x, y);
}
inline void work() {
	ll pos;
	int x, y;
	cin>>pos;
	if(pos>(ll)n*m-n)
		x=pos-((ll)n*m-n), y=0;
	else if(pos<=m-1)
		x=0, y=pos;
	else
		query(0, 1, 1, 0, pos-(m-1), x, y);
	cout<<x+1<<" "<<y+1<<"\n";
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m>>q;
	while(q--) work();
	cout.flush();
	return 0;
}
posted @ 2022-01-05 23:44  JustinRochester  阅读(37)  评论(0编辑  收藏  举报