• 【题解】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;
    }
    
  • 相关阅读:
    WinForm的Chart控件画条形图
    WinForm的Chart控件画折线图
    自定义控件
    左侧收缩菜单
    数组
    C#生成随机数的三种方法
    WinForm之GDI手动双缓冲技术
    WinForm之GDI画图步骤
    WinForm GDI编程:Graphics画布类
    翻译:《实用的Python编程》08_02_Logging
  • 原文地址:https://www.cnblogs.com/JustinRochester/p/15769385.html
Copyright © 2020-2023  润新知