算法很美——数学问题
- 题1:天平称重
问题描述:
用天平称重时,我们希望用尽可能少的砝码组合称出尽可能多的重量。
砝码重量分别是1,3,9,27,81……3的指数次幂,每种重量砝码只有一个
则它们可以组合称出任意整数重量(砝码允许放在左右两个盘中)。
本题目要求编程实现:对用户给定的重量,给出砝码组合方案。
例如:
用户输入:
5
程序输出:
9-3-1
用户输入:
19
程序输出:
27-9+1
要求程序输出的组合总是大数在前小数在后。
可以假设用户的输入的数字符合范围1~1000000
思路:3的指数次幂——可以想到用3进制,将输入转为三进制,按位输出,而二进制中可能出现2,但同一重量砝码只有一个,考虑加一减一,如下
19的三进制表示:
2 0 1:二位加一变成:1 0 0 1
减一变成:1 -1 0 1
这样可以使三进制表示中只有1,0,-1
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int k[10],num;
void fun(int a)
{
num=0;int flag=0;
while(a)
{
if(a%3==2)
{k[num++]=-1;a/=3;a++;}
else {k[num++]=a%3;a/=3;}
}//printf("num=%d\n",num);
//for(int i=num-1;i>=0;i--)printf("%d ",k[i]);printf("\n");
for(int i=num-1;i>=0;i--)
{
//printf("i+1=%d pow=%d\n",i+1,pow(3,i+1));
if(k[i]<0)printf("-%d",(int)pow(3,i));
else if(k[i]==1)
{
if(flag)
printf("+%d",(int)pow(3,i));
else printf("%d",(int)pow(3,i));
}
flag=1;
}printf("\n");
}
int main()
{
int n;
while(scanf("%d",&n)!=-1)
fun(n);
return 0;
}
- 题2:Nim游戏
Nim游戏特点:
n个部分,任意取,至少一个,轮流,取光为赢,两人均为最优策略
思路:
所有堆进行异或
如果结果为0,则任何取法都将使之变成非零
如果结果不是零,则总有取法使之变成零
所以结果为零,先手输;结果非零,先手赢。
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
void fun(int a[],int n)
{
int t=a[0];
for(int i=1;i<n;i++)t^=a[i];
if(t)
{
printf("WIN:1\n");return;
}
else
{
printf("WIN:2\n");return;
}
}
int main()
{
int n;
while(scanf("%d",&n)!=-1)
{
int a[n];
for(int i=0;i<n;i++)scanf("%d",&a[i]);
fun(a,n);
}
return 0;
}
- 题3:Georgia ans Bob——Nim的变形
题目描述:
Georgia和Bob在玩游戏。一个无限长的棋盘上有N个旗子,第i个棋子的位置可以用Pi表示。现在Georgia先走。每个人每一次可以把一枚棋子向左移动任意个格子,但是不能超越其他棋子,也不能和其他棋子处在同一个格子里。如果轮到某一个人的时候Ta再也不能移动棋子了,就判负。现在每个测试数据给定一种情况,如果Georgia会赢,输出“Georgia will win”,如果Bob会赢,输出“Bob will win”
思路:
“直到不能移动——间隔为0”,“可以向左移动任意格,不能跨越,至少移动1格”
可以考虑用Nim问题思路解决
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
void Nim(int a[],int n)
{
int res=a[0];
for(int i=1;i<n;i++)
res^=a[i];
if(res)
{
printf("WIN:1\n");return;
}
else printf("WIN:2");
}
void fun(int a[],int n)
{
int b[n/2+1],j=0;
if(n%2==0)
{
for(int i=1;i<n;i+=2)
b[j++]=a[i]-a[i-1]-1;
}
else
{
for(int i=0;i<n;i+=2)
b[j++]=i==0?a[0]:a[i]-a[i-1]-1;
}
Nim(b,j);
}
int main()
{
int n;
while(scanf("%d",&n)!=-1)
{
int a[n];
for(int i=0;i<n;i++)scanf("%d",&a[i]);
sort(a,n);
fun(a,n);
}
return 0;
}
- 重要求和公式
等差:
平方和、立方和:
等比:
调和级数:=lnn
- gcd
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int gcd(int m,int n)
{
return n==0?m:gcd(n,m%n);
}
int main()
{
int a,b;
while(scanf("%d %d",&a,&b)!=-1)printf("%d\n",gcd(a,b));
return 0;
}
- 题3:
在两点间线段上,有几个点横纵坐标均为整数
gcd(d1,d2)-1个 - 裴蜀公式(贝祖公式)
- 素数筛——求第N个素数
求第N个素数的普通算法(看从2到根号N是否有数能将N除尽)——O(N根号N)
为提高效率可使用埃氏筛,依次筛掉2,3,4,5······的倍数,得到一个素数表——O(nlognlogn)
思路:先开空间(数组),再筛倍数
开空间大小?
有素数定理可得,在X范围内,大致有X/logX个素数
例题:求第100002个素数
#include <iostream>
#include <bits/stdc++.h>
#define N 100002
using namespace std;
void fun()
{
long n=2,i=2;
while(n/log(n)<N)
n++;
n+=5;
printf("n=%ld\n",n);
int arr[n];
memset(arr,0,sizeof(arr));
while(i<n)
{
if(arr[i]!=0)
{
i++;
continue;
}
int k=2;
while(i*k<n)
{arr[i*k]=-1;k++;}
i++;
}
long num=0;
for(int j=2;j<n;j++)
{
if(arr[j]==0)num++;
if(num==N)
{
printf("%d\n",j);
return;
}
}
printf("num=%ld\n",num);
}
int main()
{
fun();
return 0;
}
100002超时
- 快速幂——求n的m次幂
求n的平方的平方的平方···直到大于所求,剩下的幂次递归
#include <iostream>
#include <bits/stdc++.h>
#define N 100002
using namespace std;
int fun(int n,int m)
{
if(m==1)return n;
if(m==0)return 1;
int ans=n,i=1;
while(i*2<=m)
{ans*=ans;i*=2;}
return ans*fun(n,m-i);
}
int main()
{
int n,m;
while(scanf("%d %d",&n,&m)!=-1)
printf("%d\n",fun(n,m));
return 0;
}
- 快速幂方法二:巧算
例:a的10次方
10:(1010)2
所以,a的10次方等于a的2的3次方*a的2的1次方
#include <iostream>
#include <bits/stdc++.h>
int fun(int m,int n)
{
int result=1,pingfangshu=m;
if(n==0)return 1;
while(n)
{
if((n&1)==1)
result*=pingfangshu;
pingfangshu*=pingfangshu;
n>>=1;
}
return result;
}
int main()
{
int m,n;
while(scanf("%d %d",&m,&n)!=-1)
printf("%d\n",fun(m,n));
return 0;
}
- 斐波那契数列的另一种求法——类似于幂运算