这几天做了几道微不足道的数论题,感觉做法都十分的高啊,我这种僵化的思想是很难有那样的切题知识水平的。然后做了几道题,感觉也有点熟悉数学的那一套理论了,虽然我还是太弱,但是有点东西还是要讲出来的嘛,一起谈笑风生,积累人生经验。闷声发大财虽然好,但是是不支持的。
上面那句话看不懂就无视吧。
那么对于数论题,我们应该如何下手呢??我总结了一些分析技巧和优化技巧,希望有用(希望考场推得出来)。
题目分析:
1.题目给的很直接了,让你求这个那个。
以两道同年的NOI题目为例。
1.荒岛野人 Savage
反正不管怎么样,看完题目我就知道考什么了(这并不能掩饰我WA几次的事实)。一眼扩展欧几里得,数据规模也当然很支持。暴力枚举m,n2扩欧就能跑过,速度较西方记者而言也不遑多让。
#include
#include
#include
#include
#include
#include
#include
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;
const int N = 21;
int n,m,c[N],p[N],l[N];
inline int gi()
{
int x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
inline int Abs(int x){return x>0?x:-x;}
inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
inline void exgcd(int a,int b,int &x,int &y)
{
if(b==0){x=1;y=0;return;}
exgcd(b,a%b,y,x);y-=a/b*x;
}
inline bool check(int i,int j,int cir)
{
int A=(p[i]-p[j]),B=cir,C=(c[j]-c[i]),X=0,Y=0;
int d=gcd(A,B);if(C%d!=0)return true;
exgcd(A,B,X,Y);B=Abs(B/d);
X=((X/d*C)%B+B)%B;if(X==0)X+=B;
return X>min(l[i],l[j]);
}
int main()
{
n=gi();
for(int i=1;i<=n;++i)
c[i]=gi(),p[i]=gi(),l[i]=gi(),m=max(m,c[i]);
for(;;++m)
{
int flag=1;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
if(!check(i,j,m))
{flag=0;i=n+1;break;}
if(flag)break;
}
printf("%d",m);
return 0;
}
2.机器人m号 robot
讲真我认为应该批判一番这个出题人,本来考场时间就不多,还花个10分钟看题,还要记住什么军人学者政客(噫,怎么这么像他)。
独立数就是欧拉函数嘛,至于按因子数分类什么的跟莫比乌斯函数关系并不(没)大(有)。然后求出军人政客后用欧拉函数的性质求出学者的个数就可以了。
这题做法还是比较神的。欧拉函数不是是积性的嘛,它又是给你的质数因子,所以你利用积性+乘法结合律,就可以写出一个二维DP的式子。但有一个更加高的方法就是直接把答案带进去递推,相当于用那个DP式子的分奇偶前缀和直接算,数组都不用开。
#include
#include
#include
#include
#include
#include
#include
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;
const LL Mod = 10000;
LL Ans1,Ans2,m,k;
int gi()
{
int x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
void pL(LL x)
{
if(x<0)putchar('-'),pL(-x);
if(x<10)putchar(x+'0');
else pL(x/10),putchar(x%10+'0');
}
void pc(){putchar('\n');}
LL gL()
{
LL x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
LL Qpow(LL d,LL z)
{
LL ans=1;
for(;z;z>>=1,d=d*d%Mod)if(z&1)ans=ans*d%Mod;
return ans;
}
int main()
{
k=gL();m=1;
for(LL i=1;i<=k;++i)
{
LL p=gL(),e=gL();
m=m*Qpow(p,e)%Mod;
if(p==2)continue;
LL ans1=(Ans1+(Ans2+1)*(p-1))%Mod;
LL ans2=(Ans2+Ans1*(p-1))%Mod;
Ans1=ans1;Ans2=ans2;
}
pL(Ans2);pc();pL(Ans1);pc();pL(((m-Ans1-Ans2-1)%Mod+Mod)%Mod);
return 0;
}
2.公式推导
题目给了你一个公式,但是直接算是显然会T的,这个时候就要将公式等价化简。这种题目SDOI以前是有的,现在他们的Oier身经百战了,见的多了,这种问题就没怎么出现过了。但对于新手来说,对于对数论函数的高妙使用的熟悉还是有很大帮助的。
1.Longge的问题
你看,多么简单的一个问题,但显然暴力是跑不过去的,这点上不管是香港记者还是松爷也必须承认的。
那么我们必须考虑优化(不是底层优化!)。注意到有贡献的只有gcd,且关于gcd有一个优美的性质:gcd(a/gcd(a,b),b/gcd(a,b))=1;
就是说除了gcd之后两个数忽然就互质了,于是个数就是欧拉那么多个。
于是答案就是sigma(d|n) d*phi(n/d);phi在线爆算就可以了。
#include
#include
#include
#include
#include
#include
#include
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;
LL n,Ans;
LL gL()
{
LL x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
LL get(LL x)
{
LL ans=x;
for(LL i=2;i*i<=x;++i)
if(x%i==0)
{
ans=ans/i*(i-1);
while(x%i==0)x/=i;
}
if(x!=1)ans=ans/x*(x-1);
return ans;
}
int main()
{
n=gL();
for(LL i=1;i*i<=n;++i)
if(n%i==0)
{
if(i*i==n)Ans+=i*get(i);
else Ans+=(i*get(n/i)+(n/i)*get(i));
}
printf("%lld\n",Ans);
return 0;
}
2.GCD
这问的问题啊,还是太简单,太幼稚。
一个数对(x,y)的gcd为质数p,如果假设x>y,那么对于(x/p,y/p)而言答案就是很水的类似于上一题的操作了。
对于所有的数,我们用前缀和优化一下就可以了,答案再乘以2加上1就可以了。
#include
#include
#include
#include
#include
#include
#include
#define LL long long int
#define Inc i*Prime[j]
using namespace std;
const int N = 10010000;
int n,Prime[N],tot,ph[N],vis[N];
LL Q[N],Ans;
int gi()
{
int x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
LL gl()
{
LL x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
void prepare()
{
ph[1]=1;
for(int i=2;i<=n;++i)
{
if(!vis[i]){Prime[++tot]=i;ph[i]=i-1;}
for(int j=1;j<=tot;++j)
{
if(Inc>N)break;vis[Inc]=1;
if(i%Prime[j])ph[Inc]=ph[i]*ph[Prime[j]];
else{ph[Inc]=ph[i]*Prime[j];break;}
}
}
for(int i=2;i<=n;++i)
Q[i]=Q[i-1]+(LL)ph[i];
}
int main()
{
n=gi();prepare();
for(int i=1;i<=tot;++i)
Ans+=(2*(Q[n/Prime[i]])+1);
printf("%lld",Ans);
return 0;
}
3.构建数学模型,巧妙转化
这部分是最难的。就像数学原理人人都会,但数学题不见得人人都会做;谁都有过冲动,但有的人非要去肛坦克。一般碰到这种题目,不应及时下手,应该在草稿纸上理性分析,并且打好暴力验证你的思想。学长传授的人生经验是,推一个式子打一遍,不要在过程中出现偏差。
1.仪仗队
这道题简直就是莫比乌斯反演的弱化版(第一象限)的弱化版(n=m)的弱化版(n<=40000)好吗!真的不怕考场上有人打表吗?
反正一个点能被看到就是这个点横纵坐标的gcd=1才可以!你再只看一个上或者下三角,就是欧拉函数!水的一笔。
/**************************************************************
Problem: 2190
User: Ryze
Language: C++
Result: Accepted
Time:24 ms
Memory:1760 kb
****************************************************************/
#include
#include
#include
#include
#include
#include
#include
#define LL long long int
#define Inc i*Prime[j]
using namespace std;
const int N = 40100;
int n,vis[N],Prime[N],ph[N],tot,Ans;
int gi()
{
int x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
void pui(int x)
{
if(x<10)putchar(x+'0');
else pui(x/10),putchar(x%10+'0');
}
void shai()
{
ph[1]=1;
for(int i=2;i<=n;++i)
{
if(!vis[i]){Prime[++tot]=i;ph[i]=i-1;}
for(int j=1;j<=tot;++j)
{
if(Inc>n)break;vis[Inc]=1;
if(i%Prime[j])ph[Inc]=ph[i]*ph[Prime[j]];
else {ph[Inc]=ph[i]*Prime[j];break;}
}
Ans+=ph[i];
}Ans-=ph[n]-1;
}
int main()
{
n=gi();shai();
printf("%d",Ans*2+1);
return 0;
}
2.古代猪文
题面是很有趣的,充满着批判的意味,仿佛是要弄出一个大新闻,让人Excited!
在PKU的学长跟我们讲这是板子合集,前提是你要看出来。
题目显然求G的sigma{d|n}(一个组合数)次方。组合数之和做指数肯定是不支持的。因为模数是个质数,就把指数模一个phi(模数)=模数-1;又因为模数-1是个合数,且发现没有相同质因子,就用扩展Lucas来敲。然后再打一个快速幂就可以AC这道牛逼哄哄的题目惹。
#include
#include
#include
#include
#include
#include
#include
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;
const LL Mod = 999911659;
const int N = 50010;
LL n,g,M[]={0,2ll,3ll,4679ll,35617ll},Y[10];
LL J[N],A[N],Z[10];
void pL(LL x)
{
if(x<10)putchar(x+'0');
else pL(x/10),putchar(x%10+'0');
}
LL gL()
{
LL x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
void prepare(LL x)
{
A[1]=J[0]=J[1]=1;
for(LL i=2;i>=1,d=d*d%Mod)if(z&1)ans=ans*d%Mod;
return ans;
}
int main()
{
n=gL();g=gL()%Mod;
if(g==0){pL(0);return 0;}
for(int t=1;t<=4;++t)
{
prepare(M[t]);
Y[t]=get(M[t]);
Z[t]=((Mod-1)/M[t])*A[((Mod-1)/M[t])%M[t]];
Y[0]+=Y[t]*Z[t]%(Mod-1);Y[0]%=(Mod-1);
}
return pL(Qpow(g,Y[0])),0;
}
3.圆上的整点
这题真是神了系列。
首先你会想到a^2+b^2=r^2之枚举a看b。现在我们来想想优化。
a^2+b^2=r^2;
a^2=r^2-b^2;
a^2=(r+b)(r-b);
令d = gcd(右边); // 神奇思想。左式为完全平方数,就在右边构建完全平方数。
a^2=d^2*[(r+b)/d]*[(r-b)/d];
因为右边是完全平方数,两中括号内的数互质,所以本身就是完全平方数。
令其分别为p^2,q^2;
则有: p^2+q^2=2r/d;
所以你就可以直接枚举了。枚举d,再枚举p或者q就好。
#include
#include
#include
#include
#include
#include
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
#define fa (x >> 1)
#define MID LL mid=(l+r)>>1
#define max(a,b) (a>b?a:b)
#define min(a,b) (a'9' || ch<'0'){if(ch=='-')res=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
return x*res;
}
LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}
int main()
{
R=gi();r=2*R;
for(d=1;d<=sqrt(r);++d)
{
if(r%d==0)
{
LL K1=r/d;K1/=2;K1=sqrt(K1);
for(q=1;q
优化技巧
1.数论分块。
我们经常推公式推出来一个关于“取下整”的东西。其实这就是我们分块优化的突破口。
打一个表,我们可以发现,[n/i]取下整这种东西的取值并不多,有很多连续的一段其实都是一个值。经过严谨的证明,大概有2√n个。
这个时候公式就可能可以得到简化。我们用前缀和维护一下,在一段相同取值的块内把整块求出来,然后直接跳到下一个块去。
公式是:
初始化:l=1,r;
分块操作:r=n/(n/l);
下一块:l=r+1;
这便是套路所在。有此分块,O(n)的复杂度就被缩成了O(sqrt(n));数论分块在莫比乌斯和数论中应用十分广泛,考点也很多。
下面有一道经典的数论分块题目,涉及到分块后的不同操作,推荐一下:
模积和
这里放白字题解:
在此我们先了解正整数意义下的模运算,叫做取余运算。
n%p 可以用式子 n-(n/p)*p 来等效替代(仅限于整型)。
所以原式可以化简,具体式子我就给一神犇学长的博客。他推的比我的清楚,而且里面有套路。
所以接下来的就是喜闻乐见的各种分块。注意一定要一步一模。
#include
#include
#include
#include
#include
#include
#include
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;
const LL Mod = 19940417;
LL n,m,Ans;
LL gL()
{
LL x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
inline LL sum(LL l,LL r){return (r-l+1)*(r+l)/2%Mod;}
inline LL Sum(LL x){return x*(x+1)%Mod*(2*x+1)%Mod*3323403%Mod;}
inline LL calc(LL A)
{
LL ret=0;
for(LL l=1,r;l<=A;l=++r)
{
r=A/(A/l);
ret+=(r-l+1)*A%Mod-(A/l)*sum(l,r)%Mod;
ret=(ret%Mod+Mod)%Mod;
}
return ret;
}
LL cal(LL A)
{
LL res=0;
for(int l=1,r;l<=n;l=++r)
{
r=min(n/(n/l),m/(m/l));
LL ans1=n*m%Mod*(r-l+1)%Mod;
LL size=((Sum(r)-Sum(l-1))%Mod+Mod)%Mod;
LL ans2=(n/l)*(m/l)%Mod*size%Mod;
LL ans3=n*(m/l)%Mod*sum(l,r)%Mod;
LL ans4=m*(n/l)%Mod*sum(l,r)%Mod;
res=((res+ans1+ans2-ans3-ans4)%Mod+Mod)%Mod;
}
return res;
}
int main()
{
n=gL();m=gL();if(n>m)swap(n,m);
LL ans1=calc(n),ans2=calc(m);
Ans=ans1*ans2%Mod;
LL ans3=cal(n);Ans=(Ans-ans3)%Mod;
printf("%lld\n",(Ans+Mod)%Mod);
return 0;
}
2.未完待续... ...