P6583 回首过去--整数分块

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

40分 n*n暴力

不解释

80分

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

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 ) = { f c ( n A ) Y<=A f c ( n Y ) Y>A f(y)= \begin{cases} fc( \frac{n}A) & \text{Y<=A}\\ fc(\frac nY)& \text{Y>A} \end{cases} f(y)={fc(An)fc(Yn)Y<=AY>A
对于每个确定的A , ∑ Y = 1 n f c ( Y ) = A ∗ f c ( n A ) + ∑ Y = A + 1 n f c ( n Y ) \sum_{Y=1}^nfc(Y)=A*fc(\frac nA)+\sum_{Y=A+1}^nfc(\frac nY) Y=1nfc(Y)=Afc(An)+Y=A+1nfc(Yn),, ∑ Y = A + 1 n f c ( n Y \sum_{Y=A+1}^nfc(\frac nY Y=A+1nfc(Yn可以用一个后缀和维护。
然后枚举每个A,计算,总时间复杂度 O ( n ∗ l o g 2 ∗ l o g 5 ) O(n*log2*log5) O(nlog2log5)

//tjn,https://www.cnblogs.com/chdy/p/13009533.html
#include
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<<"------------"<
	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分的做法。
面对式子 X Y A X \frac{XY}{AX} AXXY,前面枚举了A,Y求X的个数,大量的计算用到了 n Y \frac nY Yn,数据范围扩大到 1 0 12 10^{12} 1012后,已经没法存储记录了,每次枚举A的时候都要分块计算他,时间复杂 2 ∗ 1 0 6 2*10^6 2106,统计了下A的个数是137,那么总时间复杂大约度 3 ∗ 1 0 8 3*10^8 3108,没有写,运气好的话也许能拿这20分。
我们发现用这种方法进行了大量重复的计算 n Y \frac nY Yn。前面分段函数Y<=A的情况也很有规律。再回到三个变量的式子A,X,Y去分析统计,可以枚举别的量吗?

如果我们枚举X,

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

#include
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) ;
			
	
	}
	cout<<ans<<endl;
	return 0;
}

你可能感兴趣的:(整数分块,数论)