暑假集训日记——8.2

心血来潮整理一下,刚入学时候做的题
威尔逊定理:(p-1)!%p=p-1
哥德巴赫猜想:

大于二的偶数可以分解为两个素数之和;
大于七的奇数可以分解为三个素数之和;(是一定可以分解成三个素数之和,也有可能分解成两个)
分解成两个必然有一个是2,其他就是至少三个。

大素数的判定和质因子分解
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>
#include<iostream>
#include<algorithm>
using namespace std;


//****************************************************************
// Miller_Rabin 算法进行素数测试
//速度快,而且可以判断 <2^63的数
//****************************************************************
const int S=20;//随机算法判定次数,S越大,判错概率越小


//计算 (a*b)%c.   a,b都是long long的数,直接相乘可能溢出的
//  a,b,c <2^63
long long mult_mod(long long a,long long b,long long c)
{
    a%=c;
    b%=c;
    long long ret=0;
    while(b)
    {
        if(b&1){ret+=a;ret%=c;}
        a<<=1;
        if(a>=c)a%=c;
        b>>=1;
    }
    return ret;
}



//计算  x^n %c
long long pow_mod(long long x,long long n,long long mod)//x^n%c
{
    if(n==1)return x%mod;
    x%=mod;
    long long tmp=x;
    long long ret=1;
    while(n)
    {
        if(n&1) ret=mult_mod(ret,tmp,mod);
        tmp=mult_mod(tmp,tmp,mod);
        n>>=1;
    }
    return ret;
}





//以a为基,n-1=x*2^t      a^(n-1)=1(mod n)  验证n是不是合数
//一定是合数返回true,不一定返回false
bool check(long long a,long long n,long long x,long long t)
{
    long long ret=pow_mod(a,x,n);
    long long last=ret;
    for(int i=1;i<=t;i++)
    {
        ret=mult_mod(ret,ret,n);
        if(ret==1&&last!=1&&last!=n-1) return true;//合数
        last=ret;
    }
    if(ret!=1) return true;
    return false;
}

// Miller_Rabin()算法素数判定
//是素数返回true.(可能是伪素数,但概率极小)
//合数返回false;

bool Miller_Rabin(long long n)
{
    if(n<2)return false;
    if(n==2)return true;
    if((n&1)==0) return false;//偶数
    long long x=n-1;
    long long t=0;
    while((x&1)==0){x>>=1;t++;}
    for(int i=0;i<S;i++)
    {
        long long a=rand()%(n-1)+1;//rand()需要stdlib.h头文件
        if(check(a,n,x,t))
            return false;//合数
    }
    return true;
}


//************************************************
//pollard_rho 算法进行质因数分解
//************************************************
long long factor[100];//质因数分解结果(刚返回时是无序的)
int tol;//质因数的个数。数组小标从0开始

long long gcd(long long a,long long b)
{
    if(a==0)return 1;//???????
    if(a<0) return gcd(-a,b);
    while(b)
    {
        long long t=a%b;
        a=b;
        b=t;
    }
    return a;
}

long long Pollard_rho(long long x,long long c)
{
    long long i=1,k=2;
    long long x0=rand()%x;
    long long y=x0;
    while(1)
    {
        i++;
        x0=(mult_mod(x0,x0,x)+c)%x;
        long long d=gcd(y-x0,x);
        if(d!=1&&d!=x) return d;
        if(y==x0) return x;
        if(i==k){y=x0;k+=k;}
    }
}
//对n进行素因子分解
void findfac(long long n)
{
    if(Miller_Rabin(n))//素数
    {
        factor[tol++]=n;
        return;
    }
    long long p=n;
    while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1);
    findfac(p);
    findfac(n/p);
}

int main()
{
    //srand(time(NULL));//需要time.h头文件//POJ上G++不能加这句话
    long long n;
    while(scanf("%I64d",&n)!=EOF)
    {
        tol=0;
        findfac(n);
        for(int i=0;i<tol;i++)printf("%I64d ",factor[i]);
        printf("\n");
        if(Miller_Rabin(n))printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}
1.快速清零技巧:memset(a,0,(n+100)*sizeof(a[0]));
/*
Zty很痴迷数学问题.。一天,yifenfei出了个数学题想难倒他,
让他回答1 / n。但Zty却回答不了^_^. 请大家编程帮助他.
Input
第一行整数T,表示测试组数。后面T行,每行一个整数 n (1<=|n|<=10^5).
Output
输出1/n. (是循环小数的,只输出第一个循环节).
*/
#include<stdio.h>
#include<string.h>
int a[1000100];
int main()
{
    int n,m,i,k,t,s;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        if(n<0)
        {
            n=-n;
            printf("-");
        }
        memset(a,0,(n+100)*sizeof(a[0]));//快速清零减少程序运行时间
        if(n==1)
        {
             printf("1\n"); continue;
        }
        printf("0.");
        s=1; k=n;
        while(s!=0)
        {
            a[s]=1; s=s*10;
            printf("%d",s/k);
            s=s%k;
            if(a[s])//判断余数是否出现过,若出现过则为循环节
                break;
        }
        printf("\n");
    }
    return 0;
}

2.1-N 中有多少个由4或7组成的数字
/*此题是判断给定的数字是第几个幸运数,首先判断位数,然后判断出现4和出现7的情况,进行统计*/

#include <iostream>
using namespace std;

long long n;
int ans;
void dfs(long long  t){
    if(t>n||t>1E9) return;
    if(t<=n)ans++;
    dfs(t*10+7);
    dfs(t*10+4);
}

int main()
{   cin>>n;
    ans=0;
    dfs(4); dfs(7);
    cout <<ans<< endl;
    //cout << "Hello world!" << endl;
    return 0;
}
//爆搜

3.报数退出
/*Description
有n个人围成一圈,按顺序从1到n编好号。从第一个人开始报数,报到3的人退出圈子,
下一个人从1开始报数,报到3的人退出圈子。如此下去,直到留下最后一个人。请按退出顺序输出退出圈子的人的编号。
Input
多组测试数据,每组输入一个整数n,表示有n个人围成一圈。
Output
请按退出顺序输出退出圈子的人的编号。
Sample Input
5
9
Sample Output
3 1 5 2 4
3 6 9 4 8 5 2 7 1
*/

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
	int n;
	while(scanf("%d",&n)!=EOF){
		int i;
		int a[n];
		//赋值
		for (i=0;i<n;i++)
			a[i]=i+1;

		int count=0,baoshu=1,xu=1;
		//报数开始
		while(count!=n-1){                                   //重点
			if(a[xu-1]!=0)//判断是否退出
			{
				//退出
				if(baoshu==3)
				{
					baoshu=1;
					count++;
					printf("%d ",a[xu-1]);//输出退出
					a[xu-1]=0;//标记退出

				}else{baoshu++;}
			}
			//加序号
				if(xu==n)
				xu=1;
				else
				xu++;
		}//重点,循环报数

		//输出最后一个
		for (i=0;i<n;i++){
			if(a[i]!=0)
	     		printf("%d\n",a[i]);
		}
	}
	return 0;
}

更快模拟,报数退出
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int num[N];
int main(){


    int T;
    cin >>T;
    while(T--){
        int n,k=233;
        scanf("%d",&n);
        for(int i = 0;i < n;i ++) num[i] = i;
        int now = 0;
        while(n>1){
            now += k-1;
            now %= n;//当报数的次数大于人数,必为从零开始避免余零的情况
            for(int i = now;i < n-1;i ++){
                num[i] = num[i+1];
            }
            n--;
        }
        printf("%d\n",num[0]+1);
    }
    return 0;
}

4.汉诺塔
/*Description
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,
在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新
摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
Input
输入圆盘数n( 1 <= n <= 10)
Output
按照示例输出搬盘子的过程,每次搬动输出一行
Sample Input
2
Sample Output
a->b
a->c
b->c
HINT
三根柱子分别标为a b c
*/
#include <stdio.h>
#include <stdlib.h>

void hntyd(int n,char a,char b,char c);
int main(int argc, char *argv[]) {
	int n;
	while(scanf("%d",&n)!=EOF){
		printf("%d\n",hnt(n));
		hntyd(n,'a','c','b');
	}
	return 0;
}


int hnt(int n){
	if(n==1)return 1;
	if(n>1)return hnt(n-1)*2+1;
}


void hntyd(int n,char a,char b,char c){
	if(n==1)printf("%c->%c\n",a,b);
	else{
		hntyd(n-1,a,c,b);//第一次递归,表示把头上n-1块从a经过c移到b
		printf("%c->%c\n",a,b);//表示把最后一块,从a移到c
		hntyd(n-1,c,b,a);//第二次递归,表示把剩下n-1块从b经过a移到c
	}
}

排队买票
/*
售票工作正在进行,每张票为50元,现在有m+n人排队等待购票,其中有m人手持50元,n人手持100元,
假设售票处不设找零,那么若想使售票处不会出现找不开零钱的局面,请你帮忙设计不同的排队方案。

特别说明的是,拿同样面值的人对换位置为同一种方案。
算法分析:
一:n=0; 那么说明买票的人都是手持50元,所以不会出现找不开零钱的局面,所以这是一种排队方案;
二:m=0; 那么说明买票的人都是手持100元,所以一定会找不开零钱,所以没有排队方案;
三:m<n; 那么说明买票的人中,手持50元的人数少于手持100元的人数,所以肯定会出现找不开零钱
的局面,所以同样没有排队方案;
四:m>n; 此时,买票的人中,手持50元的人数多于手持100元的人数,所以可以有排队方案,我们这
里来分析一下第m+n人的位置:

(1):第(m+n)人手持100元站在第(m+n-1)人的后面,那么他之前的人有(m)人手持50元,有(n-1)
人手持100元,此种情况共有f(m,n-1)种排队方式;
(1):第(m+n)人手持50元站在第(m+n-1)人的后面,那么他之前的人有(m-1)人手持50元,有(n)
人手持100元,此种情况共有f(m-1,n)种排队方式;所以通过第(m+n)人就可以分析出递归关系为:
f(m,n)=f(m-1,n)+f(m,n-1)
边界条件: 当m<n时,f(m,n)=0; 当n=0时,f(m,n)=1; 当 m=0时,f(m,n)=0;
*/
#include<stdio.h>
int paidui(int m,int n){
	int result;
	if(n==0)return 1;
	else if(m<n)return 0;
		else result=paidui(m,n-1)+paidui(m-1,n);
		return result;
}
int main(){
	int m,n;
	printf("please input m&n:");
	scanf("%d %d",&m,&n);
	printf("paidui number:%d",paidui(m,n));
	return 0;
}
佩尔方程求解
#include<stdio.h>
#include<math.h>

int main()
{
    double x,a,i;
    int t,n;
    while(scanf("%d",&t)!=EOF)
    {
        for(int j=0;j<t;j++)
        {
            scanf("%d",&n);
            for(i=1;i<=10000;i++)//降低O
            {
                a=1+n*i*i;//降低O

                x=floor(sqrt(a)+0.5);//防止double溢出
                if(x*x==a)
                {
                    printf("%.0f\n",i);
                    break;
                }
            }
            if(i>10000)
                printf("No\n");
        }

    }
    return 0;
}
/*
通过这种方法降低复杂度,减少循环,降低帧数
    同类型,百鸡问题;
    优化算法,减少时间复杂度;
*/

删除k个数,使得剩下的数最小

/*1个n位正整数a,删去其中的k位,得到一个新的正整数b,设计一个贪心算法,对给定的a和k得到最小的b;

一.先看例子:a=5476579228;去掉4位,则位数n=10,k=4,要求的最小数字b是n-k=6位的;
1、先找最高位的数,因为是6位数字,所以最高位不可能在后5位上取到(因为数字的相对顺序是不能改变的,
  假设如果取了后五位中倒数第5位的7,则所求的b就不可能是6位的了,最多也就是4位的79228)理解这点很重要!

  所以问题变成从第1位到第k+1(n-(n-k-1))取最小值,为什么是k+1,可以自己想一下。在这里就变成了
  /54765/79228在斜杠中间选择最小的数字!

2、这样根据序号1,取得最小值4,那么最高位就已经确定了是4;然后6位的数就变为还有5位要确定,
  同上边的推理过程,次高位不可能取后4位中的任何数字,因为第一位确定了是第二位上的4,所以4之
  前的数字也不可能取到了(因为所求数字的相对顺序不能发生变化),所以变为求54/7657/9228中,
  斜杠之内的数字的最小值,得到是5。3、然后取第三位数字54765/79/228,第三位取7;547657/92/28
  第四位取2;54765792/2/8,第五位只能是2;第六位就是8;则所得数字就是457228;

4、继续想一个问题如果输入的整数a是3346579228,同样n=10,k=4;会遇到什么样的问题呢?同上边的过程第一步:
  /33465/79228,此时区间内有两个相同的最小值3,该用哪个值呢?很明显应该选取第一个3,why?
  因为试想如果取第二个则,第二次就只能在4657中选择最小值,而取第一个3,则可以再34657中取得3。

5、这个算法思路大概就是这样的,算法具体该怎么实现呢?首先我们要知道程序体要循环n-k次,
  因为只有这样我们才能每次循环取出最小的数字;其次就是怎么取区间内的最小值。我这里用的是
  通过循环遍历整个区间取得最小值,最关键的是确定区间的起始位置,第一次循环的位置最好确定就是1,
  结束位置就是k+1,第二次循环的起始位置是第一次取出的最小值的坐标值加1,结束位置是k+2;
  然后继续记录最小值的坐标值,以计算下一次的起始位置。
*/
#include<iostream>
using namespace std;
int main(){
    int num,k,n=0,a[100],x;
    cin>>num>>k;
    x=num;
    //计算length(a);
     while(x>0){
      x=x/10;
      n++;
     }
     a[0]=0;
    //将输入的整形数字,存入定义的数组中;
     for(int i=n;i>0;i--)
     {
        int s=num%10;
        a[i]=s;
        num=num/10;
     }
     int j,p=0,minn[n-k+1],min,q;
     minn[0]=0;
     for(int i=1;i<=n-k;i++)//n-k次循环;
     {
        min=a[p+1];//定义q记录坐标;min[]记录每次所取的最小值
        q=p+1;
         for(j=p+1;j<=k+i;j++){
            if(a[j]<min)
            {
                min=a[j];
                q=j;
            }
        }
         p=q;
         minn[i]=min;
    }
        for(int i=1;i<=n-k;i++){
         cout<<minn[i];
     }
    return 0;
}

蛇形矩阵
#include<stdio.h>
#include<string.h>
#define maxn 20
int a[maxn][maxn];
int main()
{
    int  n,x,y,tot=0;
    scanf("%d",&n);
    memset(a,0,sizeof(a));
    tot=a[x=0][y=n-1]=1;
    while(tot<n*n)
    {
        while(x+1<n&&!a[x+1][y])
            a[++x][y]=++tot;
            //a[++x][y]=tot++;等于优先级大于++;
        while(y-1>=0&&!a[x][y-1])
            a[x][--y]=++tot;
            //a[x][--y]=tot++;
        while(x-1>=0&&!a[x-1][y])
            a[--x][y]=++tot;
            //a[--x][y]=tot++;
        while(y+1<n&&!a[x][y+1])
            a[x][++y]=++tot;
            //a[x][++y]=tot++;

    }
    for(x=0;x<n;x++)
    {
        for(y=0;y<n;y++)
            printf("%3d",a[x][y]);
            printf("\n");

    }
}
贪心 金字塔
/*
给定一个由n行数字组成的数字三角型,如图所示。
设计一个算法,计算从三角形的顶至底的一条路径
使该路径经过的数字总和最大。路径上的每一步都只能往左下或右下走,给出这个最大和。
        7
      3  8
    8  1  0
  2  7  4  4
4  5  2  6  5
这个问题来源于POJ1163。对于这种问题,我们可以有正向和反向两种思考方式。
正向思考这个问题,dp[i][j]表示从第一行第一列到第i行第j列最大的数字总和;
反向思考这个问题,dp[i][j]表示从第i行第j列到最后一行最大的数字总和。
反向思考的代码要简洁一些,在POJ上这两份代码所用时间都是32MS。
当然我们还可以对空间再进行优化,这里就不再细说了。
*/

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int triangle[110][110],dp[110][110];

int main()
{
	int N;
	cin>>N;
	memset(dp,0,sizeof(dp));
	memset(triangle,0,sizeof(triangle));
	for(int i=1;i<=N;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>triangle[i][j];
		}
	}
	for(int i=1;i<=N;i++)
	{
		dp[N][i]=triangle[N][i];
	}
	for(int i=N-1;i>=1;i--)
	{
		for(int j=1;j<=i;j++)
		{
			dp[i][j]=max(dp[i+1][j]+triangle[i][j],dp[i+1][j+1]+triangle[i][j]);
		}
	}
	cout<<dp[1][1]<<endl;
}



调整队列

/*
Description
这天小g遇到了一个队列,小g觉得队列乱糟糟的不好看。于是小g希望将队列调整成为一个等差数列
(差可为0)。但是小g对每个数都最多调整一次,每次可以对这个数加一、减一。请你帮助小g解决这
个问题,如果能调整出等差队列,输出需要调整的数的最小数量,否则输出-1。
Input
第一行一个整数n(2 <= n <= 100000),表示数列中数的个数;
第二行为n个整数pi (1 <= pi <= 1e9)。
Output
输出一个整数,表示操作数量的最小值。如果不存在则输出-1。
*/

#include<bits/stdc++.h>
using namespace std;
const int MAX_N=3e5+9;
const int INF=1e9+9;
int vec[MAX_N];
int res[MAX_N];
int N,M,T,S;
queue<int> que;

int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    while(cin>>N)
    {
        for(int i=0;i<N;i++)
        {
            scanf("%d",&vec[i]);
        }
        if(N==1 ||N==2)
        {
            cout<<0<<endl;
            continue;
        }
        int ax=INF;
        for(int a=0;a<3;a++)
        {
            for(int b=0;b<3;b++)
            {
                int t=vec[1]+b-1-(vec[0]+a-1);//全排列,公差
                int pre=vec[1]+b-1;//第一个数
                bool f=true;
                int ans=abs(a-1)+abs(b-1);//改变次数
                //cout<<pre<<"..."<<t<<"...."<<ans<<endl;
                for(int i=2;i<N;i++)
                {
                    int temp=vec[i]-pre;//每项的公差
                    if(abs(temp-t)>1)//如果不相等且差值大于一就终止
                    {
                        //cout<<vec[i]<<".!!!!..."<<pre<<endl;
                        f=false;
                        break;
                    }
                    ans+=abs(pre+t-vec[i]);//改变次数
                    pre=pre+t;
                }
                if(f) ax=min(ax,ans);
            }
        }
        if(ax!=INF)cout<<ax<<endl;
        else cout<<-1<<endl;
    }
}


最长公共子序列
 /*
若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk}是X的子序列是指存在一个严格递增下标序列
{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij(j是i的下标)。

例如对序列X={A,B,C,B,D,A,B},子序列Z={B,C,D,B},相应的递增下标序列为{2,3,5,7}。
给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
给定2个序列X={x1, x2,…, xm}和Y={y1,y2,…,yn},需要找出X和Y的最长公共子序列。

例如X=(A, B, C, B, D,A, B),Y=(B, D,C, A,B,A),最长公共子序列是BCBA,长度为4。POJ1458就是
一道LCS的入门题。考虑最长公共子序列问题如何分解成子问题,设A=a0a1…am-1,B=b0b1…bm-1,并设
Z=z0z1…zk-1为它们的最长公共子序列。

不难证明有以下性质:
如果am-1=bn-1,则一定有zk-1=am-1=bn-1,且z0z1…zk-2是a0a1…am-2和“b0b1…bn-2的一个最长公共子序列;

如果am-1!=bn-1,则若zk-1!=am-1,蕴涵z0z1…zk-1是a0a1…am-2和b0b1…bn-1的一个最长公共子序列;

如果am-1!=bn-1,则若zk-1!=bn-1, 蕴涵z0z1…zk-1是a0a1…am-1和b0b1…bn-2的一个最长公共子序列。

这样,在找A和B的公共子序列时,
如有am-1=bn-1,则找a0a1…am-2和b0b1…bn-2的最长公共子序列加1即可
如果am-1!=bn-1,则要解决两个子问题,找出a0a1…am-2和b0b1…bn-1的一个最长公共子序列和找出
a0a1…am-1和b0b1…bn-2的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一
个子问题的值求得的,以决定搜索的方向。我们是自底向上进行递推计算,那么在计算c[i,j]之前,
c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i]=Y[j]还是X[i]!=Y[j],就可以
计算出c[i][j]。
*/

#include<cstring>
#include<iostream>
#define MAXV 1000
using namespace std;
int dp[MAXV][MAXV];
char s1[MAXV],s2[MAXV];
bool issame(int a,int b)
{
    return a==b?1:0;
}
int max(int a,int b,int c)
{
    if(a>=b&&a>=c) return a;
    if(b>=a&&b>=c) return b;
    return c;
}

int main()
{
    int len1,len2,i,j;
    while(cin>>s1>>s2)
	{
        memset(dp,0,sizeof(dp));
        len1=strlen(s1);
        len2=strlen(s2);
        for(i=1;i<=len1;i++)
		{
  			for(j=1;j<=len2;j++)
  			{
                dp[i][j]=max(dp[i-1][j-1]+issame(s1[i-1],s2[j-1]),dp[i-1][j],dp[i][j-1]);
            }
        }
		cout<<dp[len1][len2]<<endl;
    }

}
/*
还有一个例子:
回文词是一种对称的字符串——也就是说,一个回文词,从左到右读和从右到左读得到的结果是一样的。
任意给定一个字符串,通过插入若干字符,都可以变成一个回文词。比如字符串Ab3bd,
在插入两个字符后可以变成一个回文词(dAb3bAd)或(Adb3bdA)。然而,插入两个以下的字符无法使它
变成一个回文词。写一个程序,求出将给定字符串变成回文词所需插入的最少字符数。把原串翻转,
再求原串和翻转后串的最长公共子序列,即原串中的最长的回文词。用原串长度减去最长公共子序列
长度就是最后答案
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值