主要学习了数论关于整数和同余和求最大公约数的几种方法。
- 整数
看了一道关于密码箱的题
题目大意:有一个密码箱,0到n-1中的某些整数是它的密码。且满足,如果a和b都是它的密码,那么(a+b)%n也是它的密码(a,b可以相等)某人试了k次密码,前k-1次都失败了,最后一次成功了。问:该密码箱最多有多少不同的密码。
这道题看了好久的解析:才看懂了但还是有一点不会。
主要是推理得到了两条结论
1.如果x是密码那么gcd(x,n)也是密码,
2.如果x,y是密码那么gcd(x,y)也是密码。
主要做法是:首先令l=GCD(a[k],n),处理出l的所有因子,然后筛去这些因子中为GCD(l,不是密码的数)的因子(因为根据结论2,这些因子绝对不是密码),然后找出最小的因子,为k,输出n/k。
下面的代码不适用stl写的但是用到了low_bound经过查询意思是:lower_bound(a+1,a+n+1,x),a为一个数组,这个函数的作用是返回在a[1]—a[n+1]第一个比x小的数的地址,用的时候注意要先排序。
代码如下:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<queue>
#include<vector>
#include<climits>
#include<string>
#include<cstdlib>
#include<ctime>
using namespace std;
long long a[500000],q[1000005],f[1000005],i,j,ans;
long long tot,n,k;
long long gcd(long long a,long long b)
{
return b? gcd(b,a%b):a;
}
int main()
{
scanf("%lld%lld",&n,&k);
for(i=1;i<=k;i++)
scanf("%lld",&a[i]);
a[k]=gcd(a[k],n);
for(i=1;i<k;i++)
a[i]=gcd(a[i],a[k]);
for(i=1;i*i<=a[k];i++)
if(a[k]%i==0)
{
q[++tot]=i;
if(i*i!=a[k]) q[++tot]=a[k]/i;
}
sort(q+1,q+tot+1);
for(i=1;i<k;i++)
f[lower_bound(q+1,q+tot+1,a[i])-q]=1;
for(i=1;i<=tot;i++)
if(f[i])
for(j=1;j<i;j++)
if(q[i]%q[j]==0)
f[j]=1;
for(ans=1;f[ans];ans++);
printf("%lld\n",n/q[ans]);
return 0;
}
2.同余
概念:若a,b为两个整数,且他们的差能被某个自然数m所整除,则称a就模m来说同余于b,或者说a和b关于模m同余。记作记作a≡b(mod m)。
同余的一些性质:
性质1:a≡a(mod m),(反身性)
性质2:若a≡b(mod m),那么b≡a(mod m),(对称性)。
性质3:若a≡b(mod m),b≡c(mod m),那么a≡c(mod m),(传递性)。
性质4:若a≡b(mod m),c≡d(mod m),那么a±c≡b±d(mod m),(可加减性)。
性质5:若a≡b(mod m),c≡d(mod m),那么ac≡bd(mod m)(可乘性)。
性质6:若a≡b(mod m),那么an≡bn(mod m),(其中n为自然数)。
性质7:若ac≡bc(mod m),(c,m)=1,那么a≡b(mod m),(记号(c,m)表示c与m的最大公约数)。
性质8:若a≡b(mod m),那么a的n次方和b的n次方也对于m同余。
性质9:若a≡b(mod m)、c≡d(mod m)、e≡f(mod m)……x≡y(mod m),
那么:a+c+e+……+x和b+d+f+……+y也对于m同余。
3.最大公约数的求法。
辗转相除法:
int gcd(int x,int y)
{
return y==0?x:gcd(y,x%y);
}
二进制算法(感觉应该会更有效率)
具体的实现如下
若x,y均为偶数,则gcd(x,y)=*gcd(x/2,y/2)'
x偶y为奇gcd(x,y)=gcd(x/2,y);
x为奇y为偶gcd(x,y)=gcd(x,y/2);
均为奇数gcd(x,y)=gcd(x-y,y);
核心代码如下:
其中&的意思为:if (y&1) :位与运算,1就是0000000001.
如果y的最后一位为xxxxxxx0,则结果为false,最后为xxxxxxxx1结果为true.
二进制中最后1位为0为偶数,为1是奇数,所以这个可以用来判断数的奇偶性
其中>>的含义:
>>是右移运算符。
假设x=5,那么x的二进制为0101,x>>1表示x右移1位,即把最右边一位的1删掉,变为010,此时x=2;
x>>=1等价于x=x>>1,跟x+=1等价于x=x+1是一个道理
int gcd(int a,int b){
int c=1;
while(a-b)
{
if(a&1)
{
if(b&1)
{
if(a>b)a=(a-b)>>1;else b=(b-a)>>1;
}
else b>>=1;
}
else
{
if(b&1)a>>=1;else c<<=1,a>>=1,b>>=1;
}
}
return c*a;
}
3.就是自己做的一点题。
题意:大概是输入两个数,如m,n,把m拆成1到n之间输的和,问一共有多少种。
这道题看了很长时间,依然不知道怎么做,所以就csdn了一下。但是看了代码也没有完全看懂,等慢慢的把动态规划弄明白了,会继续可这道题。但是大体的思路是知道的,
csdn上的代码如下:
#include <iostream>
#define LIMIT_ULL 100000000000000000
using namespace std;
static const int K_MAX = 101;
static const int N_MAX = 1001;
long long dp[N_MAX][2]; //0是高位,1是低位
int K,N;
void solve()
{
dp[0][1]=1;
for(int i=1;i<=K;i++)
{
for(int j=i;j<=N;j++)
{
// 完全背包问题的套路
dp[j][1] += dp[j-i][1];
//处理进位
dp[j][0] += dp[j-i][0] + dp[j][1] / LIMIT_ULL;
dp[j][1] %= LIMIT_ULL;
}
}
//两数合并成一个大数的输出
if(dp[N][0]) cout<<dp[N][0];
cout<<dp[N][1]<<endl;
}
int main()
{
cin>>N>>K;
solve();
}
今天所学的大概就是这些吧,感觉还是没有完全弄懂,尤其是一些题,感觉同余是刚接触但是并不是很难理解,但看了一些关于同余的例题感觉还是挺难做的。还有就是心看到的用二进制去求最大公约数,相较于辗转相除法,会更加有效率。再有就是有一道poj上关于动态规划的题目,csdn上的代码还没有完全理解。