心血来潮整理一下,刚入学时候做的题
威尔逊定理:(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)。然而,插入两个以下的字符无法使它
变成一个回文词。写一个程序,求出将给定字符串变成回文词所需插入的最少字符数。把原串翻转,
再求原串和翻转后串的最长公共子序列,即原串中的最长的回文词。用原串长度减去最长公共子序列
长度就是最后答案
*/