小G的约数——牛客练习赛77
题目链接:https://ac.nowcoder.com/acm/contest/11160/C
大意
已知F(n)为n的约数和,G(n)=F(1)+F(2)+…+F(n-1)+F(n)
求G(G(n)).
思路
n最大时易知G(n)=2056198403,常规约数和求法复杂度为O(n),很明显不能用来求G(G(n))。
这时候需要引入整数分块的思路,O( n \sqrt{n} n)的复杂度完全可以胜任。
具体整数分块证明就不做了,说一点可以直接拿来用的东西。
n t = n t ′ \frac{n}{t}=\frac{n}{t'} tn=t′n 中,t’的最大取值可通过n/(n/t)得到(就不证了)
另外,通过常规约数和求法我们知道,i因子在1 ~ n范围内的个数为n/i个。通过上式可以得到相同因子个数时的在1 ~ n中的一个范围。
比如n=10时,若t=3,那么t’=3,即[3,3]范围内拥有相同因子个数3;若t=4,那么t’=5,即[4,5]范围内拥有相同因子个数2.
那么,我们既知道了因子个数相同的范围,又知道了该范围内表示因子个数,我们只需要将其全部加起来即可。怎么加也需要一些技巧,暴力累加肯定也是不行的,因为[t,t’]范围可能很大,累加相当于根本没优化。观察一下我们可以知道,因子就是[t,t’]内数本身,比如上面的[4,5],因子个数为2,因子为4、5,我们又知道因子序列为一个等差数列,那么我们只需要对这等差序列求和再乘上因子个数就能得到该部分结果了。
详细可看下面代码
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1000000007;
ll solve(ll x)
{
ll ans=0;
for(ll i=1;i<=x;)
{
ll num=x/(x/i);//右边界
ans+=(x/i)*(i+num)*(num-i+1)/2;//x/i为因子个数,后面为等差数列求和公式
i=num+1;//跳到右边界下一个点,找下一个范围
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
ll n;
cin>>n;
cout<<solve(solve(n))<<endl;
return 0;
}
数码——美团2017年CodeM大赛-资格赛
题目链接:https://ac.nowcoder.com/acm/problem/13221
大意
给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
思路
每次处理一个数码,每次出现的次数为1 ~ r的约数个数减去1 ~ l-1的约数个数。
每个数码维护一个上限和一个下限,如[1,1],[10,19],[100,199],注意上限不能大于r。
还是一个整数分块,看代码可能更好理解,如下。
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1000000007;
ll l,r;
ll solve(ll m,ll x)
{
ll ans=m/x;//最开始为个位,直接求约数个数
for(ll st=x*10,en=min(m,x*10+9);st<=m;st=st*10,en=min(m,en*10+9))//st为下限,en为上限
{
ll j;
for(ll i=st;i<=en;i=j+1)
{
j=min(m/(m/i),en);//注意有界限是可能越界的,判断一下
ans+=(j-i+1)*(m/i);
}
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
cin>>l>>r;
for(ll i=1;i<=9;i++)
{
cout<<solve(r,i)-solve(l-1,i)<<endl;
}
return 0;
}