7.30学习总结

主要学习了数论关于整数和同余和求最大公约数的几种方法。

  1. 整数

看了一道关于密码箱的题

题目大意:有一个密码箱,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上的代码还没有完全理解。

 

猜你喜欢

转载自blog.csdn.net/qq_40859951/article/details/81317496