【已整理】一道非常重要的基础数论题,很多题目的优化都与本题有关

lightoj1245

设bound=sqrt(n),则所有的n/i中,大于等于bound的数如果出现了,则只会出现一次。小于等于bound的数如果出现了,则会出现大于等于1次。注意等于bound在这里两边都可以算进去,所以对于本题,如果利用此规律来计算,会当bound==n/bound时,会重复计算,需要特判减去bound

直接上代码吧

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

int main(){
    int t;
    ll n;
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        scanf("%lld",&n);
        ll res=0;
        ll bound=(ll)sqrt(n);
        for(ll i=1;i<=bound;i++){
            res+=n/i;
            res+=i*(n/i-n/(i+1));
        }
        if(n/bound==bound)
            res-=bound;
        printf("Case %d: %lld\n",cas,res);
    }
    return 0;
}

这里有个很重要的结论

n/i==b,则b表示的是所有满足n/k==i的k中最大的

所以枚举i时,商为i的个数即为(n/i-n/(i+1)),再乘上i即可

而大于等于bound的数都只会出现一次,所以直接加上n/i

本题还有一个更好懂的写法,但是比较慢,不过也可以AC

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

int main(){
    int t;
    ll n;
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        scanf("%lld",&n);
        ll curdiv,curend,res=0;
        for(ll i=1;i<=n;i=curend+1){
            curdiv=n/i;
            curend=n/curdiv;
            res+=(curend-i+1)*curdiv;
        }
        printf("Case %d: %lld\n",cas,res);
    }
    return 0;
}

curdiv表示当前的商,curend表示商为curdiv时,i的最大值

本题如果暴力算,就是O(n)的,当前的算法的时间复杂度取决于n/i的个数。n/i的个数我们可以近似的用n的因子数去估计,即最大不超过2*sqrt(n),所以这是一个根号级别的优化。

此后如果碰到枚举n/i的题,就可以考虑这个根号n的分块优化

系数可能是需要求个前缀和,也可能是直接提出来

 

容斥原理7.hdu6051 TrickGCD

题意非常简单,直接看上面的截图吧,基于最经典的gcd容斥问题修改得来。如果直接套用经典方法,很快可以得到一个n方的算法。

枚举gcd,从大到小递推进行隐式容斥。计算每个GCD i的时候,dp[i]=π(a[j]/i),根据题目的数据量,这样n方的算法过不了。

然后我就觉得肯定不是这么做的,其实是可以优化的。

除以i,想下取整,也就是说以i为循环节的自然数,想下取整除以i都是得到相同的结果,我们利用这一良好的性质进行优化。先记录一下每个数出现了多少次,然后求这个统计数组的前缀和。然后用n*调和级数的复杂度去计算dp[i]即可。

 

在学习了n/i的重要性质后,我再来理解一下本题

首先我能写出n方的算法

#include <bits/stdc++.h>
using namespace std;

typedef long long int ll;
const int MAXN=100005;
const ll mod=1e9+7;
int a[MAXN];
ll cnt[MAXN];
ll powerOfTwo[MAXN];
ll dp[MAXN];

int main(){
    int t,n;
    scanf("%d",&t);
    powerOfTwo[0]=1;
    for(ll i=1;i<MAXN;i++)
        powerOfTwo[i]=(powerOfTwo[i-1]*2)%mod;
    for(int cas=1;cas<=t;cas++){
        memset(cnt,0,sizeof(cnt));
        memset(dp,0,sizeof(dp));
        scanf("%d",&n);
        int maxnum=0;
        for(int i=0;i<n;i++){
            scanf("%d",a+i);
            maxnum=max(maxnum,a[i]);
        }
        ll res=0;
        for(int i=maxnum;i>=2;i--){
            dp[i]=1;
            for(int j=0;j<n;j++)
                dp[i]=(dp[i]*(a[j]/i))%mod;
            for(int j=i+i;j<=maxnum;j+=i)
                dp[i]=(dp[i]-dp[j]+mod)%mod;
            res=(res+dp[i])%mod;
        }
        printf("Case #%d: %lld\n",cas,res);
    }
    return 0;
}
View Code
for(int j=0;j<n;j++)
    dp[i]=(dp[i]*(a[j]/i))%mod;

这一层循环怎么优化呢?出现了/i运算。但是分子是a[j]。怎么办呢?我们把a数组放到一个桶里,记录每个数出现的次数,那么有一段连续区间的数/i的取值是相同的,我们处理出前缀和,就可以计算出有多少个这样的数,然后用快速幂算下贡献乘上去就行了。

这个和上面所说的/i优化有些不同,上面的/i,i是从1连续增加的,但是这里i不变,分子变化,也有相同的结论

今天CF的D题又要用到这个优化,愣是没想起来。。。事不过三啊

http://codeforces.com/contest/851/problem/D

题意:n个数,每次删除一个数的代价是y,每次给一个数加1的代价是x(这个我好像说反了,主要是下面的推导是这么设的变量,读入的时候两个反着读即可),问你最少需要多少代价,使得所有数的最大公因数大于1

枚举因数,对于每一个因数,可以得到下列公式

,a取遍这n个数

这里出现了a/d这样的东西,这就是本文所讲的优化所能应用的情况。

枚举因数,对于一个因数d,k*d+1~(k+1)*d这个区间所有的数除以d的结果都是一样的,利用这一特征进行优化

考察这个式子,x都是一样的,我们可以不管,枚举了因数d后,每种情况的d也是一样的,也可以不管

要实现上述优化,我们枚举d的倍数k(从0直至上限),则k*d+1~(k+1)*d之间所有数的的值都是(k+1)*d,那么我们用一个桶记录每个数出现了多少次,然后求前缀和,就可以O(1)的求出k*d+1~(k+1)*d这个区间内的数的贡献是(k+1)*d*N,N是这个区间内的数的个数。

而-a这一部分,意思就是k*d+1~(k+1)*d这个区间内的数的和,这个我们也可以通过维护前缀和求出来。

现在还有一个棘手的问题,因为还要考虑y的影响,如果第二项太大,我们是不要第二线而是要y的。但是很好,第二项在k*d+(k+1)*d这个区间内有良好的单调性。

设这个区间内的一个数为k*d+g,1<=g<=d。那么第二项就是((k+1)*d-(k*d+g))*x=(d-g)*x

当(d-g)*x<=y时,我们才可以用上述优化,可以算出,对于每个因数,我们都可以算出g,按照g将每个k定下的区间分成两部分,前一部分取第一项,后一部分取第二项。

上代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <cstdlib>
const int MAXN=2000005;
using namespace std;
typedef long long int ll;
const ll INF=1e18+9LL;

int a[MAXN];
ll cnt[MAXN];


int main(){
    /*
    freopen("in.txt","w",stdout);
    cout<<"500000 1000000000 1000000000"<<endl;
    for(int i=0;i<500000;i++)
        cout<<"1 ";
    cout<<endl;
    */
    //freopen("in.txt","r",stdin);
    int n,y,x;
    int p;
    scanf("%d%d%d",&n,&y,&x);
    memset(a,0,sizeof(a));
    memset(cnt,0,sizeof(cnt));
    for(int i=0;i<n;i++){
        scanf("%d",&p);
        a[p]++;
        cnt[p]+=p;
    }
    for(int i=1;i<MAXN;i++){
        a[i]+=a[i-1];
        cnt[i]+=cnt[i-1];
    }
    int rat=y/x;
    ll res=INF;
    for(int d=2;d<=1000000;d++){//枚举因数
        ll tt=0;
        for(int j=0;j<=1000000;j+=d){
            int g=max(d-rat,1);
            tt+=1LL*(a[j+d]-a[j+g-1])*(j+d)*x;
            tt-=1LL*(cnt[j+d]-cnt[j+g-1])*x;
            tt+=1LL*(a[j+g-1]-a[j])*y;
        }
        res=min(res,tt);
    }
    printf("%I64d\n",res);
}
View Code

 

posted @ 2017-10-18 20:44  nearlight  阅读(128)  评论(0)    收藏  举报