数论某些题目
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 }
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; }
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 }
注意:我们只使用前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; }