题解:P3764 签到题 III

题目传送门。

写的比较细节,篇幅可能比较长喵。

题意简述

如下定义一个函数:

\[f(x,y) = \begin{cases} 0, \quad &x=y \\ f(x-y,2y)+1, \quad &x>y \\ f(2x,y-x)+1, \quad &x<y \end{cases} \]

但当函数死循环时,同样返回值为 \(0\)

给出正整数 \(n\),求 \(\sum^{n}_{i=1}\sum^{n}_{j=1}{f(i,j)}\) 的值。

思路

显然做这个题是需要一些结论的。下面将一步步推导可能的结论。

  1. 将函数的操作口语化,会发现递归进去的函数两个值就是把大数字与小数字作差后小数字翻倍。由此观之,有 \(f(x,y)=f(y,x)\) 成立。由此可得如下式子:

\[f(x,y) = \begin{cases} 0, \quad &x=y \\ f(|x-y| , 2 \min (x,y) )+1, \quad &x \neq y \end{cases} \]

  1. 这个式子启发我们定义一个操作,将 \((x,y)\) 改为 \((|x-y| , 2 \min (x,y) )\) 并且在 \(x=y\) 时终止。显而易见,函数值仅仅与操作次数相关。仔细观察会发现,在递归的过程中,\(f(x,y)\)\(x+y\) 是一个定值。这一点为第五步的想法带来了一定启发。

  2. 这个操作的过程中我可以将每一步的每一个数字都乘上一个 \(a\) 而且操作的次数不发生改变。因此 \(f(x,y) = f(ax,ay) , a \in N_+\) 成立。

  3. \(f(ax,ay) = f(x,y) , a \in N_+\) 的已知结论,我们可将 \(f(x,y)\) 转化为 $ f(\frac{x}{\gcd (x,y)},\frac{y}{\gcd (x,y)}) $ 从而使得研究对象暂时转化为 \(x \perp y\) 的特殊情况。下面仅对 \(x \perp y\) 时进行讨论

  4. 手搓了一组数据 \(f(7,11)\) 发现是死循环。之前推导过,函数在 \(x=y\) 时终止,又因为 \(x+y\) 是定值,从而 \(f(7,11)\) 最后会在 \(f(9,9)\) 时终止。但是无论如何也无法使 \(f(x,y)\) 变为 \(f(9,9)\),因此是死循环。

  5. 从特例入手,考虑什么情况下会死循环。

    • \(x+y\) 为奇数时,不存在 \(x,y \in \mathbb{Z}\) 使得 \(x=y\),所以会死循环;

    • \(x+y\) 为偶数时,必须要是 \(2^{k+1}\) 的形式才不会死循环且值为 \(k\)
      考虑证明该结论。

      由于 \(f(x, y) = f(y, x)\),不妨设 \(x < y\) 从而 \(f(x, y) = f(2x, y - x) + 1\)

      由于 \(x \perp y\)\(x+y\) 为偶数,则只能是 \(x,y\) 均为奇数。故 \(2x,y-x\) 均为偶数,则可转化为 $ f(\frac{x}{\gcd (x,y)},\frac{y}{\gcd (x,y)}) $ 的形式,即 \(f(x,y) = f(x,\frac{y - x}{2})\) 成立。

      继续观察发现,\(x+\frac{y - x}{2}=\frac{x + y}{2}\) 成立。因此上述操作就是将 \(x+y\) 减半。显然如果不会死循环,就可以通过一直这么减半直到 \(x+y=2^1\) 使得 \(f(x, y) = f(1, 1) = 0\)

      对于初始的 \(x,y\),若 \(x + y = 2^{k+1}\) 则会减半 \(k\) 次到达 \(2^1\) 从而 \(f(x,y) = k\),而若 \(x + y \not = 2 ^ {k + 1}\) 则无法减半到 \(f(1,1)\),换言之会在中途遇到一个 \(x,y\) 一奇一偶的情况,从而 \(x+y\) 为奇数导致进入死循环。

  6. 由上一条的推导可得:

\[f(x,y) = \begin{cases} 0, \quad &x + y \not = 2^{k+1} \\ k, \quad &x + y = 2^{k+1} \end{cases} \]

  1. 回到第四条,去除当时的假设,下面对 \(\forall x,y\) 进行讨论
    若 $ \gcd(x,y) \not = 1$ 则约去 \(\gcd(x,y)\)。反过来,我们对每一组互素的 \(x, y\) 就直接往上乘就可以了。

思路推到这里就大差不差了,然后就是代码实现了。感觉写起来有点数论分块的意味,对小范围枚举,对大范围优化。

代码

#include<bits/stdc++.h>
#define ll unsigned long long
using namespace std;

ll _2(int x){return 1ll<<x;}

ll solve(ll n){
    ll x=floor(log2(n))/2;
	ll y=n/((_2(x+1))+1);
	ll l=_2(x)-1,r;
    ll ans=0;
    // 小范围枚举
	for(int i=1;i<=x;i++){
        ll j=_2(i-1),k=_2(i);
		for(j;j<k;j++) ans+=n/(j<<1|1)*i;
    }
    x++;
    // 大范围优化
	while(y){
		if(n/((_2(x+1))-1)==y){
			ans+=x*y*(_2(x)-1-l);
			l=_2(x)-1,x++;
		}
        else{
			r=(n/y-1)>>1;
			ans+=x*y*(r-l);
			y--,l=r;
		}
	}
    return ans<<1; // 补全有序数对贡献,即交换后还有一组。
}

int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll n;cin>>n;
    cout<<solve(n);
    return 0;
}

完结撒花花!

posted @ 2025-12-30 17:22  Circle_Table  阅读(2)  评论(0)    收藏  举报