算法相关 资源和一些以前记的题

这篇博客整理了算法比赛中的部分题目和解题思路,包括DFS、DP等算法的应用,并提供了HDU、POJ等算法题库的简介,以及一些算法网站的推荐,如Codeforces和LeetCode。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法比赛就此不打啦,学了点皮毛混了个省一国二就跑路了(蓝桥杯退役也太真实了),要是能早点知道算法比赛这类东西就好了,大一大二也不至于这么混过去,这样相识与分别也挺奇妙的,因为也没多久,也没什么好怀念的了。把之前写过的算法的东西全放这里吧,纪念一下吧。

各种算法网站:
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]));
        }
    }
}

高位水仙花数求解

算法思路:
以三位的水仙花数为例——

  1. 432各位立方幂和为99,小于100,则不能使用;
  2. 433各位立方幂和为118,但118并不由4 3 3三个数组成,则不是水仙花数;
  3. 531各位立方和为153,且153由5 3 1组成,则是水仙花数;
  4. 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
故 ** 5
x^12%13≡5 **。

中间的13x^5本来就是13的倍数,根据费马小定理他被5整除的结果:
x^(5-1)%5≡1
故 ** 13
x^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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值