数论某些题目

Posted CHADLZX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数论某些题目相关的知识,希望对你有一定的参考价值。

1.平面上有一个圆, 圆心坐标为(0,0),半径为n. 问圆周上有多少个整点. 整点的定义即x,y坐标均为整数的点.

 

做题过程:思考后感觉有点难度,可能用到一些奇怪的知识,于是决定去看题解;

题解:

1.一般人都能想到x2+y2=r2这一点,但是仅想到这一点并没什么用;

2.变形:

   移项得:r2-x2=y2

   得y2=(r-x)(r+x)

   设d=gcd((r-x),(r+x))

   则设A=(r-x)/d,B=(r+x)/d

   代入原式得 y2=ABd2

   因为gcd(A,B)=1

   所以A,B均为完全平方数

   设a2=A,b2=B

   可得a2+b2=2r/d

3.得到上面的式子,d很显然是2r的约数,可以枚举d;

在枚举d时,可以枚举a,算出b,若gcd(a,b)等于1,则ans++即可;

个人理解:实际上它这个最后的式子相比于最初的式子有什么优点呢?

第一个是r的次数由2次方变成了一次方,于是就可以发现原来的一个O(n)级别的算法成了根号级别的算法;

本质上是通过数学运算除去原本枚举时的无用状态;

代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<string>
 5 #include<ctime>
 6 #include<algorithm>
 7 #include<cstdlib>
 8 #include<map>
 9 #include<set>
10 #include<vector>
11 #include<queue>
12 using namespace std;
13 #define FILE "dealing"
14 #define LL long long 
15 #define up(i,j,n) for(int i=j;i<=n;i++)
16 #define pii pair<int,int>
17 #define piii pair<int,pair<int,int> >
18 template<typename T> inline bool chkmin(T &a,T b){return a>b?a=b,true:false;}
19 namespace IO{
20     char *fs,*ft,buf[1<<15];
21     inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
22     inline int read(){
23         int x=0,ch=gc();bool f=0;
24         while(ch<\'0\'||ch>\'9\'){if(ch==\'-\')f=1;ch=gc();}
25         while(ch<=\'9\'&&ch>=\'0\'){x=(x<<1)+(x<<3)+ch-\'0\';ch=gc();}
26         return f?-x:x;
27     }
28 }using namespace IO;
29 namespace OI{
30     LL n;
31     LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}
32     LL cnt=0;
33     void slove(){
34         scanf("%lld",&n);
35         for(LL d=1;d*d<=2*n;d++){
36             if(!(2*n%d)){
37                 for(LL a=1;a*a<=(n/d);a++){
38                     LL b=(LL)sqrt(2.0*n/d-a*a);
39                     if(a*a+b*b==2*n/d&&gcd(a,b)==1&&a!=b)cnt++;
40                 }
41                 d=2*n/d;
42                 for(LL a=1;a*a<=(n/d);a++){
43                     LL b=(LL)sqrt(2.0*n/d-a*a);
44                     if(a*a+b*b==2*n/d&&gcd(a,b)==1&&a!=b)cnt++;
45                 }
46                 d=2*n/d;
47             }
48         }
49         cnt=4*cnt+4;
50         printf("%lld\\n",cnt);
51     }
52 }
53 int main(){
54     freopen(FILE".in","r",stdin);
55     freopen(FILE".out","w",stdout);
56     using namespace OI;
57     slove();
58     return 0;
59 }
View Code

 2.一共有4种硬币,面值分别为c1,c2,c3,c4. 阿Q带着一些硬币去商店买东西,他带了d1枚第一种硬币,d2枚第二种硬币,d3枚第三种硬币,d4枚第四种硬币,若想买一个价值为s的东西,问阿Q有多少种付coins的方法.
比如c={1,2,5,10},d={3,2,3,1},s=10,一共有4种方法:
10=1+1+1+2+5
10=1+2+2+5
10=5+5
10=10
注意,阿Q可能会去很多次商店,每次带的硬币数量和要买的东西价值可能不一样,你需要对每一次都求出方法总数。

 

以前做过的题目再做一遍;

题解:

1.先预处理出来f数组,f[i]表示这四种面值的钱凑成i面额有多少种方案;

2.然后利用容斥原理,对每次询问,用全部限制的方案数-限制第一种物品的方案数-限制第二种物品的方案数-限制第三种物品的方案数-限制第四种物品的方案数+限制第一,二种物品的方案数......+限制1,2,3,4物品的方案数;

累计完之后便是答案;

为什么?

以限制第一种物品为例,它需要凑成s钱数,如果使用(d[1]+1)份以上的1物品,就不被允许,因此就需要用f[s]减去使用四种硬币凑成s-(d[1]+1)*c[1]的方案数,同理减去2,3,4种硬币不符要求的方案数;

但会发现多减了,因为有类似第一种第二种物品都超过了限制的情况被减去了两次,因此要加回来......容斥原理大致就是这么个意思;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<algorithm>
#include<cstdlib>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;
#define FILE "dealing"
#define LL long long 
#define up(i,j,n) for(int i=j;i<=n;i++)
#define pii pair<int,int>
#define piii pair<int,pair<int,int> >
template<typename T> inline bool chkmin(T &a,T b){return a>b?a=b,true:false;}
namespace IO{
    char *fs,*ft,buf[1<<15];
    inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
    inline int read(){
        int x=0,ch=gc();bool f=0;
        while(ch<\'0\'||ch>\'9\'){if(ch==\'-\')f=1;ch=gc();}
        while(ch<=\'9\'&&ch>=\'0\'){x=(x<<1)+(x<<3)+ch-\'0\';ch=gc();}
        return f?-x:x;
    }
}using namespace IO;
namespace OI{
    const int maxn(101000);
    LL c[5],d[5],f[maxn],q,s;
    void slove(){
        up(i,1,4)c[i]=read();q=read();
        f[0]=1;
        up(k,1,4)up(i,c[k],100000)f[i]+=f[i-c[k]];
        while(q--){
            up(i,1,4)d[i]=read();
            s=read();
            LL ans=0;
            for(int i=0;i<(1<<4);i++){
                LL cnt=0,sum=0;
                up(j,1,4)if(i&(1<<(j-1)))cnt++,sum+=(d[j]+1)*c[j];
                sum=s-sum<0?0:f[s-sum];
                if(cnt%2==0)ans+=sum;
                else ans-=sum;
            }
            printf("%lld\\n",ans);
        }
        
    }
}
int main(){
    freopen(FILE".in","r",stdin);
    freopen(FILE".out","w",stdout);
    using namespace OI;
    slove();
    return 0;
}
View Code

 

 

 

3.[haoi2008]糖果传递

老师准备了一堆糖果, 恰好n个小朋友可以分到数目一样多的糖果. 老师要n个小朋友去拿糖果, 然后围着圆桌坐好, 第1个小朋友的左边是第n个小朋友, 其他第i个小朋友左边是第i-1个小朋友。 大家坐好后, 老师发现, 有些小朋友抢了很多的糖果, 有的小朋友只得到了一点点糖果, 甚至一颗也没有, 设第i个小朋友有ai颗糖果. 小朋友们可以选择将一些糖果给他左边的或者右边的小朋友, 通过”糖果传递”最后使得每个小朋友得到的糖果数是一样多的, 假设一颗糖果从一个小朋友传给另一个小朋友的代价是1, 问怎样传递使得所耗的总代价最小

 

这题训练指南上有;

题解:

排除一下多余的情况,我们可以设x[i]表示i给i-1一共a[i]个糖果,a[i]小于0表示反过来给;

于是可以列式:
A[i]+x[i+1]-x[i]=S;

n个方程,n个未知数,是否可以消元?

肯定是不行的,这是一个圈上的糖果交换,前n-1个方程一定能推出第n个方程;

而且如果能这么解出来,我们还怎么保证sum(x[i])最小;

所以肯定不行;

不过我们可以发现未知数太多,难以找到规律,尝试消元;

......

x[3]=x[2]-(A[2]-S);

x[2]=x[1]-(A[1]-S);

x[1]=x[1];

这样就可以递推用x[1]表示其他未知数;

可以这么搞:

x[1]=x[1];

x[2]=x[1]-c[2];

x[3]=x[1]-c[3];

x[4]=x[1]-c[4];

......

这样很明显看出来我们要求的答案就是数轴上一堆点,x[1]到所有点距离之和;

这样很容易看出x[1]取所有点中位数的时候答案最小;

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<string>
 5 #include<ctime>
 6 #include<algorithm>
 7 #include<cstdlib>
 8 #include<map>
 9 #include<set>
10 #include<vector>
11 #include<queue>
12 #include<cmath>
13 using namespace std;
14 #define FILE "dealing"
15 #define LL long long 
16 #define up(i,j,n) for(int i=j;i<=n;i++)
17 #define pii pair<int,int>
18 #define piii pair<int,pair<int,int> >
19 template<typename T> inline bool chkmin(T &a,T b){return a>b?a=b,true:false;}
20 template<typename T> inline bool chkmax(T &a,T b){return a<b?a=b,true:false;}
21 namespace IO{
22     char *fs,*ft,buf[1<<15];
23     inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
24     inline int read(){
25         LL x=0;int ch=gc();bool f=0;
26         while(ch<\'0\'||ch>\'9\'){if(ch==\'-\')f=1;ch=gc();}
27         while(ch<=\'9\'&&ch>=\'0\'){x=(x<<1)+(x<<3)+ch-\'0\';ch=gc();}
28         return f?-x:x;
29     }
30 }using namespace IO;
31 namespace OI{
32     const int maxn(1010000);
33     LL a[maxn],n,c[maxn],M,S,sum,ans=0;
34     void slove(){
35         n=read();
36         up(i,1,n)a[i]=read(),M+=a[i];
37         M/=n;
38         up(i,1,(n-1))c[i]=c[i-1]+a[i]-M;
39         sort(c,c+n);
40         sum=c[n/2];
41         up(i,0,(n-1))ans+=abs(sum-c[i]);
42         printf("%I64d\\n",ans);
43     }
44 }
45 int main(){
46     freopen(FILE".in","r",stdin);
47     freopen(FILE".out","w",stdout);
48     using namespace OI;
49     slove();
50     return 0;
51 }
View Code

注意:我们只使用前n-1个公式即可;

 

 

 

给你一对数a,b,你可以任意使用(a,b), (a,-b), (-a,b), (-a,-b), (b,a), (b,-a), (-b,a), (-b,-a)这些向量,问你能不能拼出另一个向量(x,y)。
说明:这里的拼就是使得你选出的向量之和为(x,y)

 

 

题解:

首先这八个向量是不必都用上的,去除那些加负号后等同的向量后剩下了四个(a,b)(a,-b)(b,a)(b,-a)。

最后要组成的向量是(x,y),由向量运算法则设这样的式子:

(x,y)=A(a,b)+B(a,-b)+C(b,a)+D(b,-a);

可化成:

x=a(A+B)+b(C+D)

y=b(A-B)+a(C-D)

设x1=A+B,y1=C+D,x2=A-B,y2=C-D;

x=ax1+by1;

y=bx2+ay2;

然后我们就可以看出来了,如果A,B,C,D有解,

那么x1+x2,y1+y2都是偶数;

然后利用拓展gcd求出一组x1,y1,x2,y2,

判断一下即可;

细节上主要注意直接求出的一组解可能不符条件,但变形后就符合条件了

由于判断的是奇偶,每个方程的解最多两种情况,乘起来一共四种情况,都需要判断;

详见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<algorithm>
#include<cstdlib>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;
#define FILE "dealing"
#define LL long long 
#define up(i,j,n) for(int i=j;i<=n;i++)
#define pii pair<LL,LL>
#define piii pair<LL,pair<LL,LL> >
template<typename T> inline bool chkmin(T &a,T b){return a>b?a=b,true:false;}
template<typename T> inline bool chkmax(T &a,T b){return a<b?a=b,true:false;}
namespace IO{
    char *fs,*ft,buf[1<<15];
    inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
    inline int read(){
        LL x=0;int ch=gc();bool f=0;
        while(ch<\'0\'||ch>\'9\'){if(ch==\'-\')f=1;ch=gc();}
        while(ch<=\'9\'&&ch>=\'0\'){x=(x<<1)+(x<<3)+ch-\'0\';ch=gc();}
        return f?-x:x;
    }
}using namespace IO;
LL a,b,x,y,t;
namespace OI{
    void gcd(LL a,LL b,LL& d,LL &x,LL &y){
        if(!b){d=a,x=1,y=0;return;}
        gcd(b,a%b,d,x,y);
        LL t=x;
        x=y;
        y=t-a/b*x;
    }
    char check(){
        LL d,x1,y1,x2,y2;
        gcd(a,b,d,x1,y1);
        x2=y1,y2=x1;
        a/=d,b/=d;
        if(x%d||y%d)return \'N\';
        x1*=x/d,y1*=x/d,x2*=y/d,y2*=y/d;
        if(!((x1+x2)&1)&&!((y1+y2)&1))return \'Y\';
        x1+=b,y1-=a;
        if(!((x1+x2)&1)&&!((y1+y2)&1))return \'Y\';
        x2+=a,y2-=b;
        if(!((x1+x2)&1)&&!((y1+y2)&1))return \'Y\';
        x1+=b,y1-=a;
        if(!((x1+x2)&1)&&!((y1+y2)&1))return \'Y\';
        return \'N\';
    }
    void slove(){
        t=read();
        while(t--){
            a=read(),b=read(),x=read(),y=read();
            printf("%c\\n",check());
        }
    }
}
int main(){
    freopen(FILE".in","r",stdin);
    freopen(FILE".out","w",stdout);
    using namespace OI;
    slove();
    return 0;
}
View Code

 

以上是关于数论某些题目的主要内容,如果未能解决你的问题,请参考以下文章

数论分块之整除分块

LeetCode数论题目总结

XidianOJ 1098 突击数论前的xry111

数论——HDU - 2136

php 有趣的代码片段在某些时候可能会有用。

翻牌的数论推倒题——319. 灯泡开关