P6583 回首过去--整数分块

题目来源:P6583 回首过去.
根据样例分析,可以xy\frac xy是有限小数的条件是分母只能包含因子2和5,直觉证明,整数在进行除法的过程中,如果需要去补0,则相当于*10,而10只包含因子2和5,所以出现其他因子就不是有限循环小数。

40分 n*n暴力

不解释

80分

符合条件的xy\frac xy可以分解为XY2p5qX\frac{XY}{2^p5^qX}其中KaTeX parse error: Expected '}', got 'EOF' at end of input: {X%2||X%5!=0},
A=2p5qA=2^p5^q,,这样我们有三个变量:在A、X、Y的个数。
且有隐含条件:AX<=n,XY<=n.
我们在A,Y确定的情况统计X的个数,A的取值范围大约为log2log5log2*log5
例如
n=20,A=1,2,4,5,8,10,16,20,
根据X的取值特点,X=1,3,7,9,11,13,17,19。
可以用容斥函数计算X允许的取值情况fc(X)=XX2X5+X10fc(X)=X-\frac X2-\frac X5+\frac X{10}
举个实例来感性理解下。

A=1 时候

Y=1——》X=1…n/A;1,3,7,9,11,13,17,19
Y=2——》X=1…n/2;1,3,7,9,
Y=3——》X=1…n/3;1,3,
Y=4——》X=1…n/4;1,3,
Y=5——》X=1…n/5;1,3,
Y=6——》X=1…n/6;1,3,
Y=7——》X=1…n/7;1

A=2 时候

Y=1——》X=1…n/2;1,3,7,9
Y=2——》X=1…n/2;1,3,7,9,
Y=3——》X=1…n/3;1,3,
Y=4——》X=1…n/4;1,3,
Y=5——》X=1…n/5;1,3,
Y=6——》X=1…n/6;1,3,
Y=7——》X=1…n/7;1

设f(Y)为A,Y确定时候符合要求想X数的个数。

f(y)={fc(nA)Y<=Afc(nY)Y>Af(y)= \begin{cases} fc( \frac{n}A) & \text{Y<=A}\\ fc(\frac nY)& \text{Y>A} \end{cases}
对于每个确定的A ,Y=1nfc(Y)=Afc(nA)+Y=A+1nfc(nY)\sum_{Y=1}^nfc(Y)=A*fc(\frac nA)+\sum_{Y=A+1}^nfc(\frac nY),,Y=A+1nfc(nY\sum_{Y=A+1}^nfc(\frac nY可以用一个后缀和维护。
然后枚举每个A,计算,总时间复杂度O(nlog2log5)O(n*log2*log5)

//tjn,https://www.cnblogs.com/chdy/p/13009533.html
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN=10010;
ll n,ans,cnt;
ll a[MAXN],s[10000009];
inline ll calc(ll x) {
	return x-x/2-x/5+x/10;
}

ll work(ll x){//fy=  n/t;n/y  
	ll w1=n/x;     
	ll t=n/w1,ans=0;
	ans=t*calc(n/x);
	ans+=s[t+1];
/*	for(int i=t;i<=n;i++){
		ans+=calc(n/i);
		cout<<"------------"<<i<<" "<<n/i<<" calc="<<calc(n/i)<<endl;
	}
	cout<<x<<":"<<ans<<endl; 
	*/
	return ans;
}
signed main() {

	cin>>n;
	for(ll i=1; i<=n; i=i*2)
		for(ll j=1; i*j<=n; j=j*5)a[++cnt]=i*j;
	sort(a+1,a+1+cnt);
	for(int i=n;i>0;i--)
		s[i]=s[i+1]+calc(n/i);
	for(int i=1;i<=cnt;i++){
		ans+=work(a[i]);
	}
	cout<<ans<<endl;
	return 0;
}

100分做法

针对80分的做法。
面对式子XYAX\frac{XY}{AX},前面枚举了A,Y求X的个数,大量的计算用到了nY\frac nY,数据范围扩大到101210^{12}后,已经没法存储记录了,每次枚举A的时候都要分块计算他,时间复杂21062*10^6,统计了下A的个数是137,那么总时间复杂大约度31083*10^8,没有写,运气好的话也许能拿这20分。
我们发现用这种方法进行了大量重复的计算nY\frac nY。前面分段函数Y<=A的情况也很有规律。再回到三个变量的式子A,X,Y去分析统计,可以枚举别的量吗?

如果我们枚举X,

符合要求的A的个数就是所有A<=nX{A<=\frac nX},随着X越来越大,数据A可以排序后统计符合要求的个数。符合要求的A会越来越少,可以单调统计。
符合要求的Y的个数就是nX{\frac nX},因此在枚举X的情况下还是nX{\frac nX}的计数形式,还是可以分块。
x=1n(A=1nXnX)\sum_{x=1}^n(\sum _{A=1}^{\frac nX }*\frac nX)
然后分块计算。
参考代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN=10010;
ll n,ans,cnt;
ll a[MAXN],s[10000009];
inline ll calc(ll x) {
	return x-x/2-x/5+x/10;
}

ll work(ll x){//fy=  n/t;n/y  
	ll w1=n/x;     
	ll t=n/w1,ans=0;
	ans=(t)*calc(n/x);
	ans+=s[t+1];
	return ans;
}
signed main() {

	cin>>n;
	for(ll i=1; i<=n; i=i*2)
		for(ll j=1; i*j<=n; j=j*5)a[++cnt]=i*j;
	sort(a+1,a+1+cnt);
	ll num=cnt;
	for(ll l=1,r;l<=n;l=r+1){
		r=n/(n/l);
		while(a[num]>n/l&&num)num--;
		//(r-l-1)*(n/l) 
	//	cout<<"num="<<num<<"*("<<calc(r)<<"-"<<calc(l-1)<<")*"<<(n/l)<<"="<<num*(calc(r)-calc(l-1))*(n/l)<<endl; 
		ans+=num*(calc(r)-calc(l-1))*(n/l) ;
			
	
	}
	cout<<ans<<endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章