NOI2010能量采集(数论)

没想到NOI竟然还有这种数学题,看来要好好学数论了……

网上的题解:

完整的结题报告:

首先我们需要知道一个知识,对于坐标系第一象限任意的整点(即横纵坐标均为整数的点)p(n,m),其与原点o(0,0)的连线上除过原点整点的个数为gcd(n,m)。其他象限上个数则为gcd(abs(n),abs(m)),这里的gcd(a,b)是指a与b的最大公约数(Greastest Common Divisor),abs(a)是指数a的绝对值。
证明:
考虑在op上最小的一个整点(x,y),这里的最小是指横纵坐标绝对值最小,x与y必然满足gcd(x,y)=1,即x与y互质。因为若不互质的话,将x与y均除去他们的公约数后可以产生一个更小的整点。则显然有(kx,ky){x<=kx<=n,k属于正整数}也在线段op上,而且这些点也是op上全部的整点,显然这些点的个数等于最大的那个k。为方便叙述我们直接将其称为k,其满足kx=n,ky=m。则显然k=gcd(n,m),证明完毕。
则现在我们只需要求出每一个gcd(n,m)即可,这里我们可以使用经典的欧几里德算法,其的复杂度为O(lgb),b为两个数中较小的那个。总的复杂度将为O(n^2lgn)但是考虑到此题的规模极大,此方法必然超时。
进一步考虑,我们不难发现要求的最大公约数的规模相对于所有数对的规模要小的多。所以我们可以转而求对于一个数p(p<min(n,m)),满足gcd(a,b)=p的数对(a,b)的个数。
令num[i]表示最大公约数为i的数对的个数,bound为min(n,m)。首先可以知道公约数有i的数对的个数应为(n div i)*(m div i),这个是比较好想的,然而并没有满足要求,因为i并不为最大公约数。不过处理方法很简单,考虑到这些当前的数对可能存在比i更大的公约数为2i,3i,4i...ki(ki<=bound),只需将这些数对删去即可。
这样我们就有了整体框架,首先可以直接求出num[bound] = (n div bound)*(m div bound),按照从大到小的顺序求num[i]即可,num[i] = (n div i)*(m div i)-Sigma(num[ki]) , 1<=ki<=bound) (那个求和的符号我打不出来...)
则代价的总和为2*Sigma(num[i]*i)-nm(因为题目中要求的线段上的整点不包括端点,而我们算的gcd(a,b),其中包括了点(a,b))。

我的代码:

 1 var n,m,ans,d:int64;
 2     g:longint;
 3     f:array[0..100005] of int64;
 4     function min(x,y:int64):int64;
 5      begin
 6      if x<y then min:=x else min:=y;
 7      end;
 8 begin
 9  ans:=0;
10  readln(n,m);
11  for g:=min(n,m) downto 1 do
12   begin
13    f[g]:=(n div g)*(m div g);
14    d:=g+g;
15    while d<=min(n,m) do
16     begin
17     dec(f[g],f[d]);
18     inc(d,g);
19     end;
20   end;
21  for g:=min(n,m) downto 1 do
22   inc(ans,f[g]*(2*g-1));
23  writeln(ans);
24 end.          
View Code

 ps:f[i]:=(n div i )* (m div i)

在这个式子中,即使 f 数组为 int64 数组,但n,m为 longint ,当n=m=100000时还是会发生溢出,所以n,m也必为 inf64

这种容易忽视的错误需要注意,即参与四则运算的变量必须能承载运算结果!

 

UPD:更简单的莫比乌斯反演

sigma(gcd(x,y))1<=x<=n,1<=y<=m=sigma(fai[k]*(n/k)*(m/k)) 1<=k<=min(n,m)

线性筛就行了。

代码:

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<string>
#define inf 1000000000
#define maxn 100000+5
#define maxm 100000+5
#define eps 1e-10
#define ll long long
#define pa pair<int,int>
#define for0(i,n) for(int i=0;i<=(n);i++)
#define for1(i,n) for(int i=1;i<=(n);i++)
#define for2(i,x,y) for(int i=(x);i<=(y);i++)
#define for3(i,x,y) for(int i=(x);i>=(y);i--)
#define for4(i,x) for(int i=head[x],y=e[i].go;i;i=e[i].next,y=e[i].go)
#define mod 1000000007
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();}
    return x*f;
}
ll n,m,p[maxn],tot,fai[maxn];
ll ans;
bool v[maxn];
inline void get()
{
    fai[1]=1;
    for2(i,2,m)
    {
        if(!v[i])p[++tot]=i,fai[i]=i-1;
        for1(j,tot)
        {
            int k=p[j]*i;
            if(k>m)break;
            v[k]=1;
            if(i%p[j])fai[k]=fai[i]*(p[j]-1);
            else {fai[k]=fai[i]*p[j];break;}
        }
    }
}
int main()
{
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    n=read();m=read();
    if(n<m)swap(n,m);
    get();
    for1(i,m)ans+=fai[i]*(n/i)*(m/i);
    cout<<n*m+2*(ans-n*m)<<endl;
    return 0;
}
View Code

 

posted @ 2014-06-08 01:00  ZYF-ZYF  Views(340)  Comments(0Edit  收藏  举报