算法比赛就此不打啦,学了点皮毛混了个省一国二就跑路了(蓝桥杯退役也太真实了),要是能早点知道算法比赛这类东西就好了,大一大二也不至于这么混过去,这样相识与分别也挺奇妙的,因为也没多久,也没什么好怀念的了。把之前写过的算法的东西全放这里吧,纪念一下吧。
各种算法网站:
HDU——经典航电,题目杂,综合,没有错误提示
POJ——北大,跟航电区别不大
USACO——美国算法题库,题目偏难,有脑筋急转弯的感觉,不适合练手但适合练脑,有错误提示
Codeforces——打CF上分很刺激,有错误提示
Hackerrank——分奴必备
牛客竞赛——天天抽奖永远不会成为欧皇,比赛多,国内体验较佳
Vjudge——帮你做了各种Online Judge总结了
各OJ题目分类总结
赛码——面试算法题
LeetCode——领扣,面试算法题,分类详细学习方便,有错误提示
DFS
Problem description
概率论老师:听懂了么?
大家:。。。
概率论是门有趣的学科,但我不想凭概率挂科,是时候认真看看书了
书里有一道这样的题目:
一俱乐部有5名一年级学生,2名二年级学生,3名三年级学生,2名四年级学生。
在其中任选5名学生,求一、二、三、四年级学生均包含在内的概率。
概率论是个细活,不过我比较粗心,经常漏掉几种情况而算错
现在我想让你编个程序列举出所有符合要求(各年级学生均包含在内)的情况
Input
首先输入一个t表示有t组样例
每组样例有两行输入
第一行输入N(1<=N<=6)表示有N个年级,紧接着按顺序输入从1到N每个年级对应的学生数量m(1<m<50)
第二行输入要抽选的学生人数n(N<=n<=总人数)
Output
用n代表一个属于n年级的学生
例如:1 2 3 4 4 代表1名一年级、1名二年级、一名三年级和两名4年级的学生的组合
对于每组样例,每种组合按年级顺序输出对应学生并用空格隔开(行尾没有多余空格),并按字典序输出所有可能的组合
Sample Input
2
4 5 2 3 2
5
1 4
2
Sample Output
1 1 2 3 4
1 2 2 3 4
1 2 3 3 4
1 2 3 4 4
1 1
简单DFS思路
先从各年级取一个人,然后再DFS各年级的剩下的人,取出来全部后排序即可。
重点在于不能重复计算,所以要从小到大执行。
Java代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int grades; //年级数
static int[] men; //各年级的人数
static int num; //总共需要的人数
static int[] dfs; //用于存放选取出来的人
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while(n-->0){
grades = sc.nextInt();
men = new int[grades+1];
for(int i = 1 ; i <= grades ; i++){
men[i] = sc.nextInt();
}
num = sc.nextInt();
dfs = new int[num+1];
for(int i = 1; i <= grades ; i++){
dfs[i] = i; //首先选好各年级一人满足题目条件
}
dfs(1);
}
}
static void dfs(int layer){
//出口:人数足够,将选好的人排序并输出
if(layer+grades>num) {
int[] tmp = Arrays.copyOfRange(dfs, 0, num+1);
Arrays.sort(tmp);
for(int i = 1 ; i <= num ; i++){
if(i!=num)
System.out.print(tmp[i]+" ");
else
System.out.print(tmp[i]);
}
System.out.println();
}else {
for (int i = 1; i <= grades; i++) {
//判断条件:该年级还有人且当前位置的数不比要填的数大(保证不重复)
if (men[i]> 1 && (i>=dfs[layer+grades-1] || layer==1)) {
men[i]--;
dfs[layer+grades] = i;
dfs(layer + 1);
men[i]++;
}
}
}
}
}
DP
HDU 2602: Bone Collector
解题思路:
交学费题。
一看就是01背包以为没什么难的,
一开始还懵了一直WA看题目条件还以为是大数才A不了,
结果原来还有体积为0的东西。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n-- > 0) {
int N = sc.nextInt();
int V = sc.nextInt();
int val[] = new int[N + 1];
int vol[] = new int[N + 1];
int dp[][] = new int[N + 1][V + 1];
for (int i = 1; i <= N; i++) {
val[i] = sc.nextInt();
}
for (int i = 1; i <= N; i++) {
vol[i] = sc.nextInt();
}
for(int i = 0 ; i <= V ; i ++){
dp[0][i] = 0;
}
for (int i = 1; i <= N; i++) {
for (int k = 0; k <= V; k++) {
if(k>=vol[i]) {
int a = dp[i - 1][k];
int b = dp[i - 1][k - vol[i]]+(val[i]);
dp[i][k] = a>b?a:b;
}else dp[i][k] = dp[i - 1][k];
}
}
System.out.println(dp[N][V]);
}
}
}
HDU 1171: Big Event in HDU
解题思路:
十分巧妙的一题,可以转换成01背包问题。
dp数组的意义:dp[MAX]=不超过MAX而能取得的最大价值
那么对全部物品遍历价值val[i],有dp[x] = max(dp[x], dp[x-val[i]]+val[i])
根据题意B就是dp[sum(总价值)/2],A就是总价值-dp[sum/2]
代码如下:
import java.util.Scanner;
import static java.lang.Math.max;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n;
while ((n=sc.nextInt())>0){
int sum = 0;
int index = 0;
int dp[] = new int[200000];
int val[] = new int[5005];
for(int i = 0 ; i < n ; i ++){
int a = sc.nextInt();
int b = sc.nextInt();
while(b-->0) {
val[index++] = a;
sum += a;
}
}
for(int i = 0 ; i < index ; i ++){
for(int j = sum/2 ; j >= val[i] ; j-=val[i]){
dp[j] = max(dp[j], dp[j-val[i]]+val[i]);
}
}
System.out.println((sum-dp[sum/2]+" "+dp[sum/2]));
}
}
}
高位水仙花数求解
算法思路:
以三位的水仙花数为例——
- 432各位立方幂和为99,小于100,则不能使用;
- 433各位立方幂和为118,但118并不由4 3 3三个数组成,则不是水仙花数;
- 531各位立方和为153,且153由5 3 1组成,则是水仙花数;
- 777各位立方和为1029,大于999,则不能使用,结束循环。
再通过预处理(初始化0~9的N次幂)+递归减少运算量。
直接看代码吧:
import java.math.BigInteger;
/**
* @class: Narcissistic
* @author: Chitose
* @date: 2018/11/11
* @description:
*/
public class Narcissistic {
public static int size = 21;
public static BigInteger powArray[] = new BigInteger[10]; // 记录0~9的size次方
public static int usedTimes[] = new int[10];// 记录0~9的使用次数
public static BigInteger powArrayTimes[][]; //记录0到9中任意数字i的N次方乘以其出现的次数j的结果(i^N*j)
public static BigInteger MAX; // size位的数字能表示的最大值
public static BigInteger MIN; // size位的数字能表示的最小值
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {// 初始化powArray[]
powArray[i] = (new BigInteger("" + i)).pow(size);
}
MIN = BigInteger.valueOf(10).pow(size - 1); // 初始化最小值
MAX = BigInteger.valueOf(10).pow(size); // 初始化最大值(不能等于这个值)
powArrayTimes = new BigInteger[10][size + 1]; //初始化powArrayTimes[][]
for (int i = 0; i < 10; i++) {
powArrayTimes[i][0] = BigInteger.valueOf(0);
for (int j = 1; j < size + 1; j++) {
powArrayTimes[i][j] = powArrayTimes[i][j - 1].add(powArray[i]);
}
}
solve(9, 0, BigInteger.ZERO);
long end = System.currentTimeMillis();
System.out.println(end-start+"毫秒");
}
//index:当前轮到的0~9数字 used:当前已使用的数字总数 now:当前各数字幂和
static void solve(int index, int used, BigInteger now){
if(index == 0){ //若index到0了则剩下的全部都是0
usedTimes[0] = size - used;
solve(-1, size, now);
usedTimes[0] = 0;
return;
}
if(used == size){
if(now.compareTo(MIN)<0) { //若现在值还是小于最小值,退出
return;
}
String strNow = now.toString();
int realUsedTimes[] = new int[10]; // 记录真实的数中0~9的使用次数
for(int i = 0 ; i < strNow.length() ; i++){
realUsedTimes[strNow.charAt(i)-'0']++;
}
for(int i = 0 ; i < 10 ; i++){
if(realUsedTimes[i] != usedTimes[i]) { //若比对真实值与使用数不同则退出递归
return;
}
}
System.out.println(now); //如果0~9的使用次数都相同才打印
return;
}
//递归主要部分
for(int i = 0 ; i < size - used ; i++){
if(now.add(powArrayTimes[index][i]).compareTo(MAX)>=0){ //如果已经超过最大值直接退出这一层递归
return;
}
usedTimes[index] = i; //使用i次index的数
solve(index-1, used+i, now.add(powArrayTimes[index][i]));
usedTimes[index] = 0; //还原使用次数
}
}
}
以21位的水仙花数做例子,输出如下:
128468643043731391252
449177399146038697307
4.155秒
欧拉函数
HDU 1098: Ignatius’s puzzle
(费马小定理)
f(x)=5x13+13*x5+kax转化成f(x)=(5x12+13*x4+k*a)*x
从1~65开始数,倘若x%65!=0,则必须要里面的内容**(5x12+13*x4+ka)%65=0**,
那么分开来看:
左边的5x^12本来就是5的倍数,根据费马小定理他被13整除的结果:
而 x^(13-1)%13≡1,
故 ** 5x^12%13≡5 **。
中间的13x^5本来就是13的倍数,根据费马小定理他被5整除的结果:
而 x^(5-1)%5≡1,
故 ** 13x^5%5≡2 **。
那么对于最后的ka我们可以这样看:
当需要他被13整除的时候,左边已经有5了,那么这时候只需要ka%13==8;
当需要他被13整除的时候,中间已经有2了,那么这时候只需要ka%5==3;
满足这两个式子的a便是满足题意的a值
Java代码如下:
import java.util.Scanner;
public class hdu1098 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in) ;
while(sc.hasNext()){
int ans = -1;
int k = sc.nextInt();
for(int a = 1 ; a <=65 ; a++){
if(k*a%5==2 && k*a%13==8){
ans = a;
break;
}
}
if(ans>0)
System.out.println(ans);
else
System.out.println("no");
}
}
}
HDU 2588: GCD
(欧拉函数)
这题真的好折腾啊…题目过于误导人去用gcd了(一用不是TLE就是MLE)
首先对于 gcd(x,n)=i ,x/i显然与n/i互斥(即gcd(x/i,n/i)=1)
那么求x/i的个数就相当于求x的个数了,
而对于gcd(x,n)!=1的x,必然是n的约数,
所以只需要遍历i->[1,n]为n的约数的,求出各个与n/i互斥的x/i个数,
即使用欧拉函数——**累计φ(n/i)**的值即可。
但在这题有10^9的那么大的数,为了优化,就只遍历到sqrt(n)即可了,对于大于sqrt(n)的i,
因为每一次遍历得到的为n的约数的i,相应的也会同样有n的约数n/i(除去了i^2=n的情况,防止重复),
那么每一次遍历就是ans += φ(n/i) 和 ans += φ(n/(n/i)),
即ans += φ(n/i) 和ans += φ(i)
Java代码如下:
import java.util.Scanner;
public class hdu2879 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int cases = sc.nextInt();
for(int i = 0 ; i <cases; i++) {
int N = sc.nextInt();
int M = sc.nextInt();
int sum = 0;
for(int j =1 ; j <= Math.sqrt(N) ; j++){
if(N%j == 0){
if(j>=M && j!= Math.sqrt(N) ){
sum += euler(N/j);
}
if(N/j >= M){
sum += euler(j);
}
}
}
System.out.println(sum);
}
}
static int euler(int num){
int ans = 1;
for(int i = 2 ; i <= num ;i++){
if(num%i==0){
ans *= (i-1);
num/=i;
while(num%i==0) {
ans *= i;
num /= i;
}
}
}
return ans;
}
}
其他东西
预处理
预处理很多地方都用得到,一般都用在那种递推问题上(就是那种f(n)需要先求f(n-1)之类的),这种情况你要是每测试一个值都重新求一次就很麻烦,不如直接预处理一遍把从1~n(看题目取n的大小)的答案获取存数组里,然后便可以直接输出答案。
Hat’s Fibonacci
预处理水题,大数,当看例题就好。
import java.math.BigInteger;
import java.util.Scanner;
/**
* @class: hdu1250
* @author: Chitose
* @date: 2018/12/24
* @description:
*/
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
BigInteger fib[] = new BigInteger[10000];
fib[1] = fib[2] = fib[3] = fib[4] = BigInteger.ONE;
for(int i = 5 ; i < 10000 ; i++){
fib[i] = fib[i-1].add(fib[i-2]).add(fib[i-3]).add(fib[i-4]);
}
while(sc.hasNext()){
System.out.println(fib[sc.nextInt()]);
}
}
}
保存计算值
这一招甚至可以用在任何题当中,将自己每一次测试的值保存下来,防止有重复的测试用例。(当然那种测试用例很大的输出小的可能就不太适用了。)
N皇后问题
经典的n皇后模板题简单DFS,但不保存每次的值就会TLE:
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* @class: hdu2553
* @author: Chitose
* @date: 2018/12/24
* @description:
*/
public class Main {
static int n;
private static int ans;
private static int[] cols;//按行来记录皇后,那么用一个一维数组就可以记录了
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Map<Integer,Integer> map = new HashMap();
while((n=sc.nextInt())!=0){
if(map.containsKey(n))
System.out.println(map.get(n));
else {
ans = 0;
cols = new int[n];
dfs(0);
System.out.println(ans);
map.put(n, ans);
}
}
}
static void dfs(int used){
if(used == n) ans++;
else for(int i = 0 ; i < n ; i++){
cols[used] = i;
if(ok(used))
dfs(used+1);
}
}
static boolean ok(int row){
for(int i = 0 ; i < row ; i++){
if(cols[row] == cols[i] || row - cols[row] == i - cols[i] || row + cols[row] == i + cols[i]) //这里有技巧,寻找是否同一对角线上的话只需要找截距相同的点就可以了,斜率都是1或-1
return false;
}
return true;
}
}