1、巧用进制
用天平称重时,我们希望用尽可能少的砝码组合称出尽可能多的重量。
如果只有5个砝码,重量分别是1,3,9,27,81
则它们可以组合称出1到121之间任意整数重量(砝码允许放在左右两个盘中)。
本题目要求编程实现:对用户给定的重量,给出砝码组合方案。
例如:
用户输入:
5
程序输出:
9-3-1
用户输入:
19
程序输出:
27-9+1
要求程序输出的组合总是大数在前小数在后。
可以假设用户的输入的数字符合范围1~121。
package org.lanqiao.algo.elementary._06_math;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Case01_天平称重 {
public static void main(String[] args) {
System.out.println(Integer.toString(1000000, 3));
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
// m1(N);
//转成3进制
final String x = Integer.toString(N, 3);
//翻转后转成字符数组
char[] arr = new StringBuilder(x).reverse().toString().toCharArray();
//容器放处理之后的0 -1 1
List<Integer> list = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
if (arr[i] == '2') {
list.add(0, -1);//-1插在开头
if (i == arr.length - 1) {
list.add(0, 1);//最后一个字符,进位
} else {
++arr[i + 1];//否则,对下一个数字加1
}
} else if (arr[i] == '3') {
list.add(0, 0);//插入0
//更高位进1
if (i == arr.length - 1) {
list.add(0, 1);
} else {
++arr[i + 1];
}
} else {
list.add(0, arr[i] - '0');
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 1) sb.append("+").append((int) Math.pow(3, list.size() - i - 1));
if (list.get(i) == -1) sb.append("-").append((int) Math.pow(3, list.size() - i - 1));
}
System.out.println(sb.substring(1));
}
//暴力解法
private static void m1(int n) {
int[] s = {0, 1, -1};
for (int a = 0; a < 3; a++) {
for (int b = 0; b < 3; b++) {
for (int c = 0; c < 3; c++) {
for (int d = 0; d < 3; d++) {
for (int e = 0; e < 3; e++) {
if (s[a] * 81 + s[b] * 27 + s[c] * 9 + s[d] * 3 + s[e] * 1 == n) {
// System.out.println(s[a] + "*81+" + s[b] + "*27+" + s[c] + "*9+" + s[d] + "*3+" + s[e] + "*1");
// return;
StringBuilder sb = new StringBuilder();
if (s[a] == 1) sb.append("81");
if (s[b] == 1) sb.append("+27");
if (s[b] == -1) sb.append("-27");
if (s[c] == 1) sb.append("+9");
if (s[c] == -1) sb.append("-9");
if (s[d] == 1) sb.append("+3");
if (s[d] == -1) sb.append("-3");
if (s[e] == 1) sb.append("+1");
if (s[e] == -1) sb.append("-1");
if (sb.charAt(0) == '+' || sb.charAt(0) == '-')
System.out.println(sb.substring(1));
else
System.out.println(sb.toString());
return;
}
}
}
}
}
}
}
}
2、Nim游戏:(有一点点博弈的感觉)
一共有N堆石子,编号为1到n,,第i堆有a【i】个石子,每一次俩人从任意一堆中取出至少一颗石子,最多取走这一堆剩下的所有石子,两个人轮流行动,取光所有石子的一方获胜,,a为先手,,给定a,假设两个人都辞去最优策略,谁会获胜?
思路: 比如 3 4 5
写成二进制全部亦或起来,,如果为0的话,都能把他弄成不为0
如果不为0,我总有办法把他变成0,比如说我在任意一堆中拿走一个石子,,结果就变成0
所以 先手:1、面临非0,这样我就能把他变成0
2、面临0,,必输
class Test{
public static void main(String[] args) {
int []A = {3,4,5};
boolean res = solve(A);
System.out.println(res);
}
static boolean solve(int [] A){
int res =0;
for(int i=0;i<A.length;i++){
res^=A[i]; //亦或,,相同为1 不同为0
}
return res !=0;
}
}
3、阶梯尼姆问题:
题目链接:http://poj.org/problem?id=1704
* Sample Input
2
3
1 2 3
8
1 5 6 7 9 12 14 17
Sample Output
Bob will win
Georgia will win
import java.util.Arrays;
import java.util.Scanner;
class Test48{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int casenum = sc.nextInt();
int [][]data = new int[casenum][];
for(int i=0;i<casenum;i++){
int k=sc.nextInt();
data[i] = new int[k];
for(int j=0;j<k;j++){
data[i][j] = sc.nextInt();
}
}
for(int i=0;i<casenum;i++){
String res = deal(data[i]);
System.out.println(res);
}
}
static String deal(int [] A){
int len = A.length;
Arrays.sort(A);
int res = 0;
if((len&1)==1){ //按位与运算 是奇数
for(int i=0;i<len;i+=2){
res^=(i==0)?(A[0] -1):(A[i] - A[i-1] -1);//把两个位置之间当成石子数,个数等于后面减前面-1
}
}else {//偶数的话
for(int i=1;i<len;i+=2){
res^=(A[i] - A[i-1] -1);
}
}
if(res ==0){
return "Bob will win";
}else {
return "Gorina will win";
}
}
}
4、求和公式:
5、欧几里得算法:(其实就是辗转相除法求最大公因子)
static long gcd(int m,int n){
return n==0?m:gcd(n,m%n);
}
static long lcm(long a, long b) {
return a * b / gcd(a, b);
}
6、欧几里得扩展算法:(裴蜀等式)
ax+by = m (x和y为未知数,)此方程称为裴蜀等式,这个方程要想有解,m必须为d的倍数(d是a和b的最大公约数)
这个等式有无穷多个整数解,每组x,y都称为裴蜀数,可用扩展欧几里得算法求得,
化简:方程 ax+by=1有整数解当且紧当a和b互素
!官方词条:
裴蜀定理(或贝祖定理,Bézout's identity)得名于法国数学家艾蒂安·裴蜀,说明了对任何整数a、b和它们的最大公约
数d,关于未知数x和y的线性不定方程(称为裴蜀等式):若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立。
它的一个重要推论是:a,b互质的充要条件是存在整数x,y使ax+by=1.
推导过程:
最后一行有水印看不清楚: x = (x0%b+b)%b
//我这里用private static class ext 因为竞赛中可能一个大Main类,不允许有其他类,所以此处采用私有的内部静态类
class test3{
public static void main(String[] args) {
try{
ext.linearequation(2,3,1);
System.out.println(ext.x+" "+ext.y);
}catch (Exception e){
System.out.println("无解");
}
}
private static class ext{
static long x;
static long y;
//扩展欧欧几里得
// 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
static long ext_gcd(long a,long b){
if(b==0){
x=1;
y=0;
return a;
}
long res = ext_gcd(b,a%b);
long x1 = x; //备份x
x = y; //4
y = x1-a/b*y; //更新y
return res;
}
//线性方程
//ax+by = m 当m为gcd(a,b)的倍数时,有解
static long linearequation(long a,long b,long m) throws Exception{
long d = ext_gcd(a,b);
//如果m不是d的倍数 ,就无解
if(m%d !=0){
throw new Exception(m+"%"+"gcd("+a+","+b+")"+"!=0 ~无解");
}
long n = m/d;
x*= n;
y*=n;
return d;
}
}
}
例题:
一步之遥
从昏迷中醒来,小明发现自己被关在X星球的废矿车里。
矿车停在平直的废弃的轨道上。
他的面前是两个按钮,分别写着“F”和“B”。
小明突然记起来,这两个按钮可以控制矿车在轨道上前进和后退。
按F,会前进97米。按B会后退127米。
透过昏暗的灯光,小明看到自己前方1米远正好有个监控探头。
他必须设法使得矿车正好停在摄像头的下方,才有机会争取同伴的援助。
或许,通过多次操作F和B可以办到。
矿车上的动力已经不太足,黄色的警示灯在默默闪烁…
每次进行 F 或 B 操作都会消耗一定的能量。
小明飞快地计算,至少要多少次操作,才能把矿车准确地停在前方1米远的地方。
请填写为了达成目标,最少需要操作的次数。
思路:当然可以暴力搜,,也可以把 97 -127 1 带入
当然,,,还可以手推,,,,
答案就是97啦
然后,,,,先上一堆概念吧,代码都在下面
//求逆元
static long inverseelement(long a,long mo)throws Exception{
long d = linearequation(a,mo,1);
x = (x%mo+mo)%mo; //保证x>0
return d;
} //我不知道这个d是啥,,,但是要求的x可能就是代码中的x,,在整个类中,他又被定义,,所以直接拿来就可以了
这个代码和上面的扩展欧几里得在同一个类中
文字说明:ax≡1 (mod p)即ax-yp=1.把y写成+的形式就是ax+py=1,为方便理解下面我们把p写成b就是ax+by=1。
就表示x是a的模b乘法逆元,y是b的模a乘法逆元。然后就可以用扩展欧几里得求了。
知道逆元怎么算之后,那么乘法逆元有什么用呢?
做题时如果结果过大一般都会让你模一个数,确保结果不是很大,而这个数一般是1e9+7,而且这个数又是个素数,
加减乘与模运算的顺序交换不会影响结果,但是除法不行。有的题目要求结果mod一个大质数,如果原本的结果中有除法,比如除以a,那就可以乘以a的逆元替代。
(除一个数等于乘它的倒数,虽然这里的逆元不完全是倒数,但可以这么理解,毕竟乘法逆元就是倒数的扩展)。
例题:http://acm.hdu.edu.cn/showproblem.php?pid=1576
Problem Description
要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1)。
Input
数据的第一行是一个T,表示有T组数据。
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。
Output
对应每组数据输出(A/B)%9973。
Sample Input
2 1000 53
87 123456789
Sample Output
7922 6060
思路:上面有手写,,大致就是(A/B)%9978 就等于B的逆元成A%9973
求B的逆元的话 直接把B和9973带入求逆元的函数就可以了
模板:
private static class ext{
static long x;
static long y;
//扩展欧欧几里得
// 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
static long ext_gcd(long a,long b){
if(b==0){
x=1;
y=0;
return a;
}
long res = ext_gcd(b,a%b);
long x1 = x; //备份x
x = y; //4
y = x1-a/b*y; //更新y
return res;
}
//线性方程
//ax+by = m 当m为gcd(a,b)的倍数时,有解
static long linearequation(long a,long b,long m) throws Exception{
long d = ext_gcd(a,b);
//如果m不是d的倍数 ,就无解
if(m%d !=0){
throw new Exception(m+"%"+"gcd("+a+","+b+")"+"!=0 ~无解");
}
long n = m/d;
x*= n;
y*=n;
return d;
}
//求逆元
static long inverseelement(long a,long mo)throws Exception{
long d = linearequation(a,mo,1);
x = (x%mo+mo)%mo; //保证x>0
return d;
}
}
此题代码:
import java.util.Scanner;
class Main{
private static class ext{
static long x;
static long y;
//扩展欧欧几里得
// 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
static long ext_gcd(long a,long b){
if(b==0){
x=1;
y=0;
return a;
}
long res = ext_gcd(b,a%b);
long x1 = x; //备份x
x = y; //4
y = x1-a/b*y; //更新y
return res;
}
//线性方程
//ax+by = m 当m为gcd(a,b)的倍数时,有解
static long linearequation(long a,long b,long m) throws Exception{
long d = ext_gcd(a,b);
//如果m不是d的倍数 ,就无解
if(m%d !=0){
throw new Exception(m+"%"+"gcd("+a+","+b+")"+"!=0 ~无解");
}
long n = m/d;
x*= n;
y*=n;
return d;
}
//求逆元
static long inverseelement(long a,long mo)throws Exception{
long d = linearequation(a,mo,1);
x = (x%mo+mo)%mo; //保证x>0
return d;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
for(int i=0;i<T;i++){
int n = sc.nextInt();
int b= sc.nextInt();
try{
ext.inverseelement(b,9973);
long x = ext.x;
System.out.println(x*n%9973);
}catch(Exception e){
e.printStackTrace();
}
}
}
}
同余方程组:跟上面的模板放到一起好了,,这里我也没有太懂
public static long linearEquationGroup(long[] r, long[] m) throws Exception {
int len = r.length;
if (len == 0 && r[0] == 0) return m[0];
long R = r[0];
long M = m[0];
for (int i = 1; i < len; i++) {
//这里往前看是两个方程
long c = r[i] - R;
long d = linearEquation(M, m[i], c);
//现在的static x是k1,用k1求得一个特解
long x0 = R + M * x;//特解-》解系:X=x0+k*lcm(m1,m2)->得新方程: X 三 x0 mod lcm
long lcm = M * m[i] / d;//这是新的m
M = lcm;
R = x0 % lcm;//x0变成正数
}
//合并完之后,只有一个方程 : X mod M = R
while (R < 0)
R += M;
return R;
}
调用的时候这么调用
public static void main(String[] args) {
long[] a = {2,3,2}; //余数的数组
long[] m = {3,5,7}; //模的数组
try{
long res =ext.linearEquationGroup(a,m);
System.out.println(res);
}catch (Exception e){
e.printStackTrace();
}
}
例题:http://poj.org/problem?id=1006&lang=zh-CN&change=true
d1+23k = x
d2+28k =x
d3+33k =x
即: x===(三条杠)d1(%23)
x===(三条杠)d2(%28)
x===(三条杠)d3(%33)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
class test3 {
private static class ext {
static long x;
static long y;
//扩展欧欧几里得
// 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
static long ext_gcd(long a, long b) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
long res = ext_gcd(b, a % b);
long x1 = x; //备份x
x = y; //4
y = x1 - a / b * y; //更新y
return res;
}
//线性方程
//ax+by = m 当m为gcd(a,b)的倍数时,有解
static long linearequation(long a, long b, long m) throws Exception {
long d = ext_gcd(a, b);
//如果m不是d的倍数 ,就无解
if (m % d != 0) {
throw new Exception(m + "%" + "gcd(" + a + "," + b + ")" + "!=0 ~无解");
}
long n = m / d;
x *= n;
y *= n;
return d;
}
//求逆元
static long inverseelement(long a, long mo) throws Exception {
long d = linearequation(a, mo, 1);
x = (x % mo + mo) % mo; //保证x>0
return d;
}
public static long linearEquationGroup(long[] r, long[] m) throws Exception {
int len = r.length;
if (len == 0 && r[0] == 0) return m[0];
long R = r[0];
long M = m[0];
for (int i = 1; i < len; i++) {
//这里往前看是两个方程
long c = r[i] - R;
long d = linearequation(M, m[i], c);
//现在的static x是k1,用k1求得一个特解
long x0 = R + M * x;//特解-》解系:X=x0+k*lcm(m1,m2)->得新方程: X 三 x0 mod lcm
long lcm = M * m[i] / d;//这是新的m
M = lcm;
R = x0 % lcm;//x0变成正数
}
//合并完之后,只有一个方程 : X mod M = R
while (R < 0)
R += M;
return R;
}
}
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
int t = 1;
List<long[]> alist = new ArrayList<long[]>();
List<Long> dlist = new ArrayList<Long>();
while (sc.hasNext()) {
long[] a = {sc.nextLong(), sc.nextLong(), sc.nextLong()};
long d = sc.nextLong();
if (a[0] == -1 && a[1] == -1 && a[2] == -1 && d == -1) {
break;
} else {
alist.add(a);//把一整个a数组都加进去了,(三个数)
dlist.add(d);
}
}
for (int i = 0; i < alist.size(); i++) {
long[] a = alist.get(i);
long d = dlist.get(i);
long[] m = {23, 28, 33};
long res = ext.linearEquationGroup(a, m);
while (res <= d) {
res += 21252;
}
System.out.println("Case" + (t++) + ":the next riple peak occurs in " + (res - d) + "days.");
}
}
}
10、质数与质因数分解模板
import java.util.HashMap;
import java.util.Map;
class test3{
public static void main(String[] args) {
boolean res = isPrime(100);
System.out.println(res);
System.out.println(primeFactor(100));
StringBuilder sb = new StringBuilder();
Map<Integer,Integer>map = primeFactor(100);
for(Map.Entry<Integer,Integer> entry :map.entrySet()){
int k = entry.getKey();
int v = entry.getValue();
for(int i=0;i<v;i++){
sb.append("*"+k);
}
}
System.out.println(sb.substring(1));
}
//测试是否为质数
static boolean isPrime(long num){
for(int i=2;i*i<=num;i++){
if(num%i==0) return false;
}
return true;
}
//质因数分解
static Map<Integer,Integer> primeFactor(int num){
Map<Integer,Integer>map = new HashMap<>();
for(int i=2;i*i<=num;i++){
while (num%i==0){
Integer v= map.get(i);
if(v==null){
map.put(i,1);
}else {
map.put(i,v+1);
}
num /= i;
}
}
return map;
}
}
11、素数的筛法
判断一段数字中素数的个数
我如果要求第10万个素数 用以前的算法复杂度在10万×log 10万
这个算法是艾氏筛法 时间差10倍
class test3{
public static void main(String[] args) {
long now = System.currentTimeMillis();
m1(100000);
System.out.println("耗时"+(System.currentTimeMillis()-now)+"ms");
}
//求第n个素数
static void m1(int N){
int n= 2;//n是我猜测的空间,,一直自己去增大
while (n/Math.log(n)<N){ //这个估计很重要
n++;
}
//开辟这么一个数组
int []arr = new int[n];
int x =2;
while (x<n){
if(arr[x]!=0){
x++;
continue;
}
int k=2;
while (x*k<n){
arr[x*k] = -1;
k++;
}
x++;
}
//筛完后,所有非素数的下标都是-1
int sum =0;
for(int i=2;i<arr.length;i++){
if(arr[i]==0){
sum++;
}
if(sum==N){
System.out.println(i);//一旦sum==N,那他的i就是我们求得数字
return;
}
}
}
}
12、快速幂运算
class Test48{
public static void main(String[] args) {
}
//之前的
static int ex(int a,int n){
if(n==1){
return a;
}
int res = 1;
int temp = a;
int exponent =1;
while ((exponent<<1)<n){
temp = temp*temp;
exponent = exponent<<1;
}
res*=ex(a,n-exponent); //之前这里是直接用的pow函数,,这次这里直接调用回去也好
return res*temp;
}
//新的办法(二进制办法)
static long ex2(long n,long m){
long pingfangshu = n;
long result = 1;
while (m!=0){
//遇到1就累成现在的幂
if((m&1)==1){//&逐位与运算
result*=pingfangshu;
}
pingfangshu = pingfangshu*pingfangshu;
//右移一位
m>>=1;
}
return result;
}
}
斐波那契与矩阵幂运算