• BZOJ 3529: [Sdoi2014]数表


    Description
    有一张 n×m 的数表,其第 i 行第 j 列(1 <= i <= n, 1 <= j <= m)的数值为
    能同时整除 i 和 j 的所有自然数之和。给定 a , 计算数表中不大于 a 的数之和。
    Input
    输入包含多组数据。
    输入的第一行一个整数Q表示测试点内的数据组数
    接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据。
    1&lt;=nm&lt;=1051&lt;=Q&lt;=2×1041 &lt; =n.m &lt; =10^5 , 1 &lt; =Q &lt; =2×10^4
    Output
    对每组数据,输出一行一个整数,表示答案模2^31的值。
    Sample Input
    2
    4 4 3
    10 10 5
    Sample Output
    20
    148

    这题出得很刁钻……

    预备知识(莫比乌斯反演)
    思路:
    莫比乌斯反演+树状数组维护。
    1.推导公式。
    2.理解树状数组的用处。
    3.线性筛中的细节。
    4.小优化。

    1.推导公式

    首先,我们设两个函数:
    F(a)= n/aF(a)=leftlfloor n/a ight floor* m/aleftlfloor m/a ight floor
    表示agcd(i,j)a|gcd(i,j)的方案数.
    f(a)f(a)表示a=gcd(i,j)a=gcd(i,j)的方案数。
    显然,F(a)=adf(d)F(a)=sum_{a|d} f(d)

    设n<=m,那么,我们忽略a的存在,总方案数就是
    d=1ni=1nj=1nf[d][gcd(i,j)==d](bool10sum_{d=1}^n sum_{i=1}^n sum_{j=1}^n f[d]*[gcd(i,j)==d](这是bool表达式,真为1,假为0)=
    d=1ni=1 n/dj=1 m/df[d][gcd(i,j)==1]sum_{d=1}^n sum_{i=1}^{leftlfloor n/d ight floor} sum_{j=1}^{leftlfloor m/d ight floor} f[d]*[gcd(i,j)==1](把d当作部分单位元或者说只有i为d的倍数且j为d的倍数时,才满足dgcdi,jd|gcd(i,j))=
    d=1nf[d]i=1 n/dj=1 m/dkikjμ[k]sum_{d=1}^n f[d]*sum_{i=1}^{leftlfloor n/d ight floor} sum_{j=1}^{leftlfloor m/d ight floor}sum_{k|i且k|j}mu[k]=
    d=1nf[d]k=1 n/dμ[k]F[kd]sum_{d=1}^n f[d]*sum_{k=1}^{leftlfloor n/d ight floor}mu[k]*F[k*d]=
    d=1nf[d]dinμ[i/d]F[i]sum_{d=1}^n f[d]*sum_{d|i}^nmu[i/d]*F[i](又把1当作单位元)=
    (改变枚举顺序)i=1nF[i]dif[d]μ[i/d]sum_{i=1}^n F[i] sum_{d|i} f[d]*mu[i/d].
    对于f[i]f[i]我们可以用整除分块来减少枚举。

    2.理解树状数组的用处

    此时再来考虑a的影响。我们可以用离线处理数据,并让a非降序排列。
    如果f[x]&lt;=af[x]&lt;=a,那么就可以用x为F[i]F[i]贡献系数。
    还是为了减少枚举,我们边线性筛,边处理f,并以约数和从小到大排序f。
    对于每个f[x]&lt;=af[x]&lt;=a,我们令x去贡献系数,并用树状数组维护系数。

    3.线性筛中的细节

    对于线性筛,我们的任务是求μfmu和f,至于μmu的求法这里不细讲(不会请翻预备知识),这里提一下f的求法。
    方法一:时间复杂度O(NlnN)O(NlogN)O(NlnN)approx O(NlogN)

    for(int i=1;i<=10000000;i++)//Eratosthenes筛法
    	for(int j=i;j<=10000000;j+=i)
    		f[j]+=i;
    

    方法二:时间复杂度O(N)O(N)
    根据约数和公式:若x=p1c1p2c2pkckx=p_1^{c_1}*p_2^{c_2}*……p_k^{c_k},
    f(x)=i=1kj=0cipijf(x)=prod_{i=1}^ksum_{j=0}^{c_i}p_i^j

    那么我们可以实现简单继承:
    pjip_j|i时,k=iypici,f(ipj)=f(k)pycy+1+f[i]=f(k)+f(i)py设k=prod_{i e y}p_i^{c_i},f(i*p_j)=f(k)*p_y^{c_y+1}+f[i]=f(k)+f(i)*p_y(读者可自行证明,其实就是代入公式,这里不赘述)
    否则,f[ipy]=f[i]f[py],)f[i*p_y]=f[i]*f[p_y](约数和是积性函数,读者亦可自行证明,其实又是代入公式)

    4.一个小优化:

    因为答案要mod 231mod 2^{31},我们就用int存答案,溢出其实是mod 232mod 2^{32}(需要理解),所以不影响答案,只是答案为负就转为正数即可。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define g getchar()
    using namespace std;
    typedef long long ll;
    const int N=1e5+10;
    const int M=2e4+10;
    const ll mod=1LL<<31;
    
    void qr(int &x)
    {
    	char c=g;x=0;bool v=0;
    	while(!(('0'<=c&&c<='9')||c=='-'))c=g;
    	if(c=='-')v=1,c=g;
    	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
    	if(v)x=-x;
    }
    
    void write(int x)
    {
    	if(x/10)write(x/10);
    	putchar(x%10+'0');
    }
    
    struct node
    {
    	int n,m,a,id;
    	bool operator <(node b)const{return a<b.a;}
    }a[M];
    
    struct pnode
    {
    	int s,id;
    	bool operator <(pnode b)const{return s<b.s;}
    }f[N];
    
    int T,n,m,prime[N/10],tot,miu[N],mo[N],ans[M],maxn,tr[N];
    //f[i]表示i的约数和,对于mo[i](mother),设i除去最小的质数的所有因子后得到k,mo[i]=f[k];
    bool v[N];
    
    void get_prime()
    {
    	miu[1]=f[1].s=1;
    	for(int i=2;i<=maxn;i++)
    	{
    		if(!v[i])prime[++tot]=i,miu[i]=-1,mo[i]=1,f[i].s=i+1;
    		for(int j=1,k;j<=tot&&(ll)i*prime[j]<=maxn;j++)
    		{
    			v[k=i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[k]=0;
    				f[k].s=mo[i] + f[i].s*prime[j];
    				mo[k]=mo[i];
    				break;
    			}
    			else 
    			{
    				miu[k]=-miu[i];
    				f[k].s=f[i].s*f[prime[j]].s;
    				mo[k]=f[i].s;
    			}
    		}
    	}
    	for(int i=1;i<=maxn;i++)f[i].id=i;
    	sort(f+1,f+maxn+1);
    }
    
    int lowbit(int x){return x&-x;}
    
    void add(int x,int v)
    {
    	while(x<=maxn)
    	{
    		tr[x]+=v;
    		x+=lowbit(x);
    	}
    }
    
    void change(int i)
    {
    	int d=f[i].id,s=f[i].s;
    	for(int j=1;j*d<=maxn;j++)if(miu[j])
    		add(d*j,miu[j]*s);
    }
    
    int solve(int x)
    {
    	int tot=0;
    	while(x>0)
    	{
    		tot+=tr[x];
    		x-=lowbit(x);
    	}
    	return tot;
    }
    
    int main()
    {
    	qr(T);
    	for(int i=1;i<=T;i++)
    	{
    		qr(a[i].n);qr(a[i].m);qr(a[i].a);a[i].id=i;
    		if(a[i].n>a[i].m)swap(a[i].n,a[i].m);
    		if(a[i].n>maxn)maxn=a[i].n;
    	}
    	sort(a+1,a+T+1);
    	get_prime();
    	for(int i=1,j=1;i<=T;i++)
    	{
    		while(f[j].s<=a[i].a&&j<=N-10)change(j++);
    		int &sum=ans[a[i].id],n=a[i].n,m=a[i].m;
    		sum=0;
    		for(int l=1,r;l<=n;l=r+1)
    		{
    			r=min(n/(n/l),m/(m/l));
    			sum+=(solve(r)-solve(l-1))*(n/l)*(m/l);
    		}
    		if(sum<0)sum+=mod;
    	}
    	for(int i=1;i<=T;i++)write(ans[i]),puts("");
    	return 0;
    }
    
  • 相关阅读:
    js-高级06-正则
    js-高级05-javaScript处理机制
    js-高级04-函数高级应用
    js-高级03-面向对象的继承
    js-高级02-贪吃蛇案例分析
    js-高级01面向对象及创建对象
    js-API 06 高级动画二
    js-API 05 高级动画
    对象数组 数组对象 增删改查 数组
    jQuery ajax请求 一般请求和自定义请求
  • 原文地址:https://www.cnblogs.com/zsyzlzy/p/12373926.html
Copyright © 2020-2023  润新知