基础算法模板 (九) —— 动态规划

背包问题

01背包问题

因为每件物品只有选与不选两种状态,所以该问题又称01背包问题。
AcWing 2. 01背包问题

在这里插入图片描述

f[i][j]:表示所有选法集合中,只从前i个物品中选,并且总体积≤j的选法的集合,它的值是这个集合中每一个选法的最大值.
状态转移方程:f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
f[i-1][j]:不选第i个物品的集合中的最大值
f[i-1][j-v[i]]+w[i]:选第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.
import java.util.Scanner;
class Main{
    static int M = 1010;
    static int v[] = new int[M];    // 体积
    static int w[] = new int[M];    // 价值
    static int f[][] = new int[M][M]; // f(i,j)表示前i件物品中,体积不超过j的最大价值
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int N = reader.nextInt(), V = reader.nextInt();
        for(int i = 1; i <= N; i ++){
            v[i] = reader.nextInt();
            w[i] = reader.nextInt();
        }
        for(int i = 1; i <= N; i ++){
            for(int j = 1; j <= V; j ++){ 
                // 第i件物品选不选
                f[i][j] = f[i - 1][j]; // 不选第i件物品
                if(v[i]<= j){ // 选第i件物品的前提是:放得下!
                    // 更新最大价值
                    f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i]] + w[i]);
                }
            }
        }
        System.out.println(f[N][V]);
        
    }
}

暴力搜索, 过一半样例

import java.util.Scanner;
class Main{
    static int M = 1010;
    static int N, V;
    static boolean st[] = new boolean[M]; // 每件物品只能被使用一次
    static int v[] = new int[M];    // 体积
    static int w[] = new int[M];    // 价值
    static long ans = 0;
    public static void dfs(int u, int sum, int capacity){
        if(capacity < 0){
            return;
        }
        if(u > N){
            if(sum > ans){
               ans = sum; 
            }
            return;
        }
        // 选第u个物品
        if(!st[u] && capacity - v[u] >= 0){
            st[u] = true;
            dfs(u + 1, sum + w[u], capacity - v[u]);
            st[u] = false;
        }
        // 不选第u个物品
        dfs(u + 1, sum, capacity);
    }
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        N = reader.nextInt();
        V = reader.nextInt();
        for(int i = 1; i <= N; i ++){
            v[i] = reader.nextInt();
            w[i] = reader.nextInt();
        }
        dfs(1, 0, V);
        System.out.println(ans);
        
    }
}

278. 数字组合
可以转化为01背包问题求方案数:

将总和 M看作背包容量;
将每个数 A i A_i Ai 看作体积为 A i A_i Ai 的物品;

import java.util.Scanner;
class Main{
    static int M = 10010;
    static int f[][] = new int[110][M]; // f(i,j)表示只考虑前i个数中,其和为j的选法的集合
    static int a[] = new int[110];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt(), m = reader.nextInt();
        for(int i = 1; i <= n; i ++){
            a[i] = reader.nextInt();
        }
        f[0][0] = 1; // 注意初始化条件
        for(int i = 1; i <= n; i ++){
            f[i][0] = 1;
            for(int j = 1; j <= m; j ++){ 
                f[i][j] = f[i - 1][j]; // 不选第i个数
                if(a[i] <= j){ // 选第i个数
                    f[i][j] += f[i - 1][j - a[i]];
                }
            }
        }
        System.out.println(f[n][m]);
        
    }
}

280. 陪审团

从大体上来看,其实就是从 n 个人中选 m 人,使得辩方总分和控方总分之差的绝对值最小。

这么看来,本题是一个 01 背包问题,我们将 n 个人看作 n 个物品,那么每个物品会有三个体积:

1. 人数,每个候选人都是 1 个人,最终要选出 m 个人
2. 辩方得分,即辩方给每个候选人打的分数 a[i]
3. 控方得分,即控方给每个候选人打的分数 b[i]

因此我们需要依次考虑每个候选人是否选入评审团,当外层循环到阶段 i 时,表示已经考虑了前 i 个
候选人的入选情况,用 bool 数组 f[j][d][p] 表示已有 j 人被选入评审团,当前辩方总分为 d、控方
总分为 p 的状态是否可行。

第 i 个候选人有选和不选两种情况,得出状态转移方程:f[j][d][p] = f[j][d][p] | f[j - 1][d - a[i]][p - b[i]]

起始状态 f[0][0][0] = true,目标是 f[m][d][p] = true,要求 |d - p| 尽量小,d + p 尽量大。

到此我们初步分析出了一个算法,但是并没有很好的利用价值这一维度,我们可以进一步优化,我们可以将
每个候选人的辩方、控方双方得分的差 a[i] - b[i] 作为体积之一,把辩方、控方双方得分的和作为该物品
的价值。

当外层循环到 i 时,设 f[j][k] 表示已经在前 i 个候选人中选出了 j 个,此时辩方与控方总分的差为 k 时,
辩方与控方总分的和的最大值。

同样有选和不选两种情况,状态转移方程:f[j][k] = max{ f[j][k], f[j - 1][k - (a[i] - b[i])] + (a[i] + b[i]) }

起始状态 f[0][0] = 0,目标是 f[m][k],满足 |k| 尽量小,当 |k| 相同时 f[m][k] 尽量大。

作者:小小_88
链接:https://www.acwing.com/solution/content/128611/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1234. 倍数问题

在这里插入图片描述 x + a i x + a_i x+ai k − a i k - a_i kai在modk下同余。

前i个数选0个模k的最大值都为0,其他都应该是负无穷,因为选0个数总和只能为0。而没选物品的总和为0 %k不可能为其他值。
1234. 倍数问题


【题目描述】
在这里插入图片描述

AcWing 1205. 买不到的数目

暴力枚举 迭代写法

【思路】
分别枚举m、n的个数
for(m个数)
for(n个数)
k = m * m个数 + n * n个数
O(N^2),只能枚举答案在1000以内的

import java.util.Scanner;
public class Main{
    static int N = 10010;
    static int []f = new int [N];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt(), m = reader.nextInt();
        for(int i = 0; i < N / m + 1; i ++)//枚举m的个数
            for(int j = 0; j < N / n + 1; j++)//枚举n的个数
            {
                int k = m * i + n * j;
                if( k >= N ) continue;
                f[k] = 1;
            }
        for(int i = N - 1; i >= 0;i --)
            if(f[i] == 0){
                System.out.println(i);
                break;
            }
    }
}


暴力枚举——递归写法

过5/10数据

import java.util.Scanner;
public class Main{
    //判断x能否由m、n组成
    public  static boolean dfs(int x, int m, int n){
        
        if( x < 0) return false;
        
        //x == 0 说明x能由若干m、n组成
        if( x == 0 ) return true;
        
        if( x >= m && dfs(x - m, m, n)) return true;
        if( x >= n && dfs(x - n, m, n)) return true;
        
        return false;
    }
    public static void main(String args[]){
        
        Scanner reader = new Scanner(System.in);
        int m = reader.nextInt(), n = reader.nextInt();
        for(int i = 1000; i >= 1; i --)
        {
            if(!dfs(i, m, n)){
                System.out.println(i);
                break;
            }
        }
        
    }
}

错误写法:

对于有返回值的递归函数,要注意对中间结果的保存和处理,只是把它扔掉了

import java.util.Scanner;
public class Main{
    //判断x能否由m、n组成
    public  static boolean dfs(int x, int m, int n){
        
        if( x < 0) return false;
        
        //x == 0 说明x能由若干m、n组成
        if( x == 0 ) return true;
       
        if( x >= m ) return dfs(x - m, m, n);
        if( x >= n ) return dfs(x - n, m, n);
        
        return false;
    }
    public static void main(String args[]){
        
        Scanner reader = new Scanner(System.in);
        int m = reader.nextInt(), n = reader.nextInt();
        for(int i = 1000; i >= 1; i --)
        {
            if(!dfs(i, m, n)){
                System.out.println(i);
                break;
            }
        }
        
    }
}


AC代码

引理

给定a,b,如果d =gcd(a, b) >1,那么其凡不是d倍数的a,b都凑不出,因此其凑不出来的数目是INF个。

结论

若a、b均为正整数且gcd(a,b) = 1, 那么 ax + by ,x >= 0, y >= 0,最大不能表示的数为 (a - 1) * (b - 1) = a*b - a - b

import java.util.Scanner;
public class Main{
   
    public static void main(String args[]){
        
        Scanner reader = new Scanner(System.in);
        int a = reader.nextInt(), b = reader.nextInt();
        //公式: (a - 1) * (b - 1) - 1 =ab -a - b
        System.out.println(a * b - a - b);
        
    }
}


质数拆分

在这里插入图片描述

f[i][j] = Math.max(f[i - 1][j - prime[i]] + 1, f[i][j]);
注意这里求的是方案数目
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    static int N = 2022;
    static int [] prime = new int[N];
    static boolean st[] = new boolean[N + 1];
    static int cnt = 0;
    static void getPrime2(int x){
      // 埃氏筛法
      for(int i = 2; i <= x; i ++){
        if(!st[i]){
          prime[++cnt] = i;
          // 质数的所有倍数
          for(int j = i + i; j <= x; j += i){
              st[j] = true;
          }
        }
        
      }
    }
    static void getPrime(int x){
      // 埃氏筛法
      for(int i = 2; i <= x; i ++){
        if(!st[i]){
          prime[++cnt] = i;
        }
        for(int j = i + i; j <= x; j += i){
            st[j] = true;
        }
        
      }
    }
    public static void main(String[] args) {
        getPrime2(N);
        // for(int i = 1; i <= cnt; i ++){
        //   System.out.println(prime[i]);
        // }
        int [][]f = new int[cnt + 1][N + 1];
        // f[0][0] = 1;
        for(int i = 1; i <= cnt; i ++){
          for(int j = 0; j <= N; j ++ ){
              f[i][j] = f[i - 1][j]; // 不使用第i个数字
              if(j >= prime[i]){
                f[i][j] = Math.max(f[i - 1][j - prime[i]] + 1, f[i][j]);
              }

          }
        }
        System.out.println(f[cnt][N]);
    }
}

质数分解

问题描述

将2019拆分为若干个两两不同的质数之和,一共有多少种不同的方法?
注意交换顺序视为同一种方法,例如2 + 2017 = 2019与
2017 +2 = 2019视为同一种方法。

【Attention】
若干个两两不同的质数之和,不止两个哦。
枚举 应当是枚举2019以内,而不是sqrt(2019)内的质数。

数的范围从2~2018,且必须是质数。两两不同,即不重复,每个质数只能最多被用一次。明显01背包。
f(i,j)表示只考虑前i个质数,和为j的选法集合,属性为方案个数。
所求即为f(K, 2019)

01背包

import java.util.Scanner;
public class Main {
    static int N = 2019;
    static long f[][] = new long[N][N + 5];
    static int a[] = new int[N];
    public static boolean check(int x){
      for(int i = 2; i * i <= x; i ++){
        if(x % i == 0){
          return false;
        }
      }
      return true;
    }
    
    public static void main(String[] args) {
        
        int K = 0; // 注意如果K是从1开始的,所以质数的个数为K-1,不是307,而是306
        // 枚举 2~2018的质数,
        for(int i = 2; i < 2019; i ++){
          if(check(i)){
            a[++K] = i;
          }
        }
        f[0][0] = 1;
        for(int i = 1; i <= K; i ++){
            for(int j = 0; j <= N; j ++){
                f[i][j] = f[i - 1][j];
                if(j >= a[i]){
                    f[i][j] += f[i - 1][j - a[i]];
                }
            }
        }
        System.out.println(f[K][2019]);
        
    }
}

爆搜

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    static int N = 2019;
    static int K, ans;
    static int f[] = new int[N];
    static boolean vis[] = new boolean[N];
    public static boolean check(int x){
      for(int i = 2; i * i <= x; i ++){
        if(x % i == 0){
          return false;
        }
      }
      return true;
    }
    public static void dfs(int u, int p, int sum){
        if(sum > 2019){
            return;
        }
        if(sum == 2019){
          ans ++;
          return;
        }
      for(int i = p; i < K; i ++){ // p保证递增选择, vis保证两个数不同
        if(!vis[i]){
          vis[i] = true;
          dfs(u + 1, i + 1, sum + f[i]);
          vis[i] = false;
        }
      }
    }
    // 直接查看另一半是否在数组中即可
    public static void main(String[] args) {
        // 枚举 2~2018的质数,
        for(int i = 2; i < 2019; i ++){
          if(check(i)){
            f[K++] = i;
          }
        }
        //System.out.println(K); // 306
        dfs(0, 0, 0);
        System.out.println(ans);
    }
}

注意这里求的是定义的集合内的元素的最大个数

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    static int N = 2022;
    static int [] prime = new int[N];
    static boolean st[] = new boolean[N + 1];
    static int cnt = 0;
    static void getPrime2(int x){
      // 埃氏筛法
      for(int i = 2; i <= x; i ++){
        if(!st[i]){
          prime[++cnt] = i;
          // 质数的所有倍数
          for(int j = i + i; j <= x; j += i){
              st[j] = true;
          }
        }
        
      }
    }
    static void getPrime(int x){
      // 埃氏筛法
      for(int i = 2; i <= x; i ++){
        if(!st[i]){
          prime[++cnt] = i;
        }
        for(int j = i + i; j <= x; j += i){
            st[j] = true;
        }
        
      }
    }
    public static void main(String[] args) {
        getPrime2(N);
        // for(int i = 1; i <= cnt; i ++){
        //   System.out.println(prime[i]);
        // }
        int [][]f = new int[cnt + 1][N + 1];
        // f[0][0] = 1;
        for(int i = 1; i <= cnt; i ++){
          for(int j = 0; j <= N; j ++ ){
              f[i][j] = f[i - 1][j]; // 不使用第i个数字
              if(j >= prime[i]){
                f[i][j] = Math.max(f[i - 1][j - prime[i]] + 1, f[i][j]);
              }

          }
        }
        System.out.println(f[cnt][N]);
    }
}

完全背包问题

模板题——3. 完全背包问题

3. 完全背包问题

import java.util.Scanner;
class Main{
    static int N = 1010;
    static int v[] = new int[N];
    static int w[] = new int[N];
    static int f[][] = new int[N][N]; // 第一维表示物品,第二维表示体积,其值为价值
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt(), t = reader.nextInt();
        for(int i = 1; i <= n; i++) {
            v[i] = reader.nextInt();
            w[i] = reader.nextInt();
        }
        for(int i = 1; i <= n; i ++){
            for(int j = 0; j <= t; j++){
                f[i][j] = f[i - 1][j]; // 不放
                for(int k = 0; k * v[i] <= j;k ++) { // 放
                    f[i][j] = Math.max(f[i][j], f[i - 1][j - k * v[i]]+ k * w[i]);
                }
            }
        }
        System.out.println(f[n][t]);
        
    }
}
k是枚举的,k的值不能无限大,因为物品不能超过背包的容量

程序中的f[i][j]本来应该是

f[i][j]=f[i-1][j];     //注意如果有这句话应该是放在第二三层for循环之间的!
f[i][j]=Math.max(f[i][j], f[i-1][j-k*v[i]]+w[i]*k);   //这句在第三层for循环内部
由于k是从0开始循环的,所以f[i][j]可以简写成
f[i][j]=Math.max(f[i][j], f[i-1][j-k*v[i]]+w[i]*k);

上述代码还可以优化为O(n*m)如下:
在这里插入图片描述

import java.util.Scanner;
class Main{
    static int N = 1010;
    static int v[] = new int[N];
    static int w[] = new int[N];
    static int f[][] = new int[N][N]; // 第一维表示物品,第二维表示体积,其值为价值
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt(), k = reader.nextInt();
        for(int i = 1; i <= n; i++) {
            v[i] = reader.nextInt();
            w[i] = reader.nextInt();
        }
        for(int i = 1; i <= n; i ++){
            for(int j = 0; j <= k; j++){
                f[i][j] = f[i - 1][j]; // 不放:划分第一个不包含i的方案
                // 划分的包含1-k个i的方案,因为可能不存在,所以需要判断一下,v[i]的大小是不是超过总容量j
                if(j >= v[i]){
                  // 注意是 f[i][j - v[i]] + w[i]而不是f[i - 1][j - v[i]] + w[i]
                    f[i][j] = Math.max(f[i][j], f[i][j - v[i]] + w[i]);
                }
            }
        }
        System.out.println(f[n][k]);
        
    }
}

包子凑数

在这里插入图片描述

在这里插入图片描述

import java.util.Scanner;
class Main{
    static int N = 110, M = 10000;
    static int a[] = new int [N];
    static boolean f[][] = new boolean[N][M];
    static int gcd(int a, int b){
        if(b == 0) {return a;}
        return gcd(b, a % b);
    }
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        for(int i = 1; i <= n; i ++) {
            a[i] = reader.nextInt();
        }
        int d = 0;
        for(int i = 1; i <= n; i ++){
            d = gcd(d, a[i]);
        }
        if(d != 1) {
            System.out.println("INF");
        }else{
            // 0个物品,重量为0,合法方案
            f[0][0] = true;
            
            for(int i = 1; i <= n; i ++){
                for(int j = 0; j < M; j ++){
                    f[i][j] = f[i - 1][j];
                    for(int k = 0; k * a[i] <= j; k ++){
                        f[i][j] |= f[i - 1][j - k * a[i]];
                    }
                }
            }
            int res = 0;
            for(int j = 1; j < M; j ++){
                if(!f[n][j]){
                    res ++;
                    // System.out.println(j);
                }
            }
            System.out.println(res);
        }
    }
}
import java.util.Scanner;

class Main{
    static int N = 10010;
    static int a[] = new int[110];
    static boolean f[][] = new boolean [110][N];
    public static int gcd(int a, int b){
        if( b == 0) return a;
        return gcd( b, a % b );
    }
    public static void main(String args[]){

        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        for(int i = 1; i <= n; i ++) a[i] = reader.nextInt();
        int d = 0; // 公约数
        
        for(int i = 1; i <= n; i ++)
            d = gcd(a[i], d);  //gcd(2,0) = 2
        if( d != 1) System.out.println("INF");
        else{
            f[0][0] = true;
            for(int i = 1; i <= n; i ++)
                for(int j = 0; j < N; j ++){
                    f[i][j] |= f[i - 1][j];
                    if( j >= a[i]) f[i][j] |= f[i][j - a[i]];
                }
            int res = 0;
            for(int i = 1; i < N; i ++)
                if( ! f[n][i] ) res ++;
            System.out.println(res);
            
            
        }
    }
}

AcWing 3382. 整数拆分(每日一题)

在这里插入图片描述

AcWing 3382. 整数拆分(每日一题)

import java.util.Scanner;
class Main{
    static int N = 1000010;
    static int k = 21;  // 2^0 ~ 2^20
    static int a[] = new int [N];
    static int f[][] = new int[21][N];
    static int mod = (int)1e9; 
   
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        a[1] = 1;
        //  
        // int k = (int)(Math.log(n + 1) / Math.log(2)); erro: 2次幂没做限制
        for(int i = 2; i <= k; i ++) {
            a[i] = a[i - 1] << 1 ;
        }
        // 完全背包问题,次幂可以使用无数次
        for(int i = 0; i < k; i ++) {
           f[i][0] = 1; // 初始化,什么都不选是一种选法
        }
        
        for(int i = 1; i < k; i ++){
            for(int j = 1; j <= n; j ++){
                f[i][j] = (f[i][j] + f[i - 1][j]) % mod;
                if(a[i] <= j){
                    f[i][j] = (f[i][j] + f[i][j - a[i]]) % mod;
                }
            }
        }
        System.out.println(f[20][n]);
    }
}

2022

在这里插入图片描述

2022

数字即为物品,每个数字的编号相当于重量,每个数字的体积为1。问题等价于从前2022个物品钟选出10个满足重量刚好为2022。
用三维数组f(i,j,k)表示从前i个物品中选出j个,其重量和为k的选法的集合,其属性为选法个数。则所求为f[2022][10][2022]

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    static int N = 2025;
    static long f[][][] = new long[N][15][N];
    public static void main(String[] args) {
        for(int i = 0; i < N; i ++) { // 容量为0(选0个物品)重量为0的方案数为1
          f[i][0][0] = 1;
        }
        int n = 2022;
        for(int i = 1; i <= n; i ++){ // 物品数
          for(int j = 1; j <= 10; j ++){  // 容量(选择的数字个数)
            for(int k = 1; k <= n; k ++){ // 重量
              f[i][j][k] = f[i - 1][j][k];  // 不选第i个数
              if(k >= i){ // 选择第i个数
                //f[i][j][k] = Math.max(f[i - 1][j - 1][k - i] + 1, f[i][j][k]);
                f[i][j][k] += f[i - 1][j - 1][k - i];

              }
            }
          }
        }
        System.out.println(f[2022][10][2022]);
    }
}

自然数拆分

在这里插入图片描述

Acwing 279. 自然数拆分

本题要求用若干个数相加为 n,求方案数。

若干个正整数就是若干个物品,要想能相加为 n,且至少两个数相加,能用上的数就是 1 ~ n - 1,共 n - 1 个物品,价值就是数值本身。
每个物品能重复使用,因此本题是经典的完全背包,直接套模板

/*
完全背包问题
集合表示: f(i,j)表示前i个数和为j的方案的集合
属性:  个数
状态计算:
    f(i,j) = f(i - 1, j)
    f(i,j) += f(i-1, j - k * a[i]) , k = 1, 2, ……
*/
import java.util.Scanner;
public class Main{
    static int N = 4010;
    static int a[] = new int[N];
    static long f[][] = new long [N][N];
    static long MOD = 2147483648L;
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        
        for(int i = 1; i <= n; i ++){
            a[i] = i;
        }
        f[0][0] = 1; // 只考虑前0个数,其和为0的方案数为1
        for(int i = 1; i <= n; i ++){
            for(int j = 0; j <= n; j ++){
                f[i][j] = f[i - 1][j];
                if(j >= a[i]){
                    f[i][j] += f[i][j - a[i]];  // 注意:完全背包是f[i][j - a[i]]而不是f[i - 1][j - a[i]]
                }
                f[i][j] %= MOD;
            }
        }
        System.out.println(f[n - 1][n]%MOD); // 必须拆为2个数,所以没有n
    }
}

数位dp

1的个数

import java.util.Scanner;
class Main{
    static int number[] = new int[15];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n  = reader.nextInt();  // 1 ~ n 出现的1的个数  int 范围是 2 ^ 31 - 1
        // 枚举 1所在的位置(个十百千万……位)
        int len = 0;
        int k = n;
        // 按从低位到高位的顺序存储
        while(k > 0){
            number[len ++] = k % 10;
            k /= 10;
        }
        long res = 0;
        for(int i = len - 1; i >= 0; i --){
            int high = 0, low = 0, t = 1;
            for(int j = len - 1; j > i; j --){
                high = high * 10 + number[j]; 
            }
            for(int j = i - 1; j >= 0; j --){
                low = low * 10 + number[j];
                t *= 10;
            }
            res += high * t;
            if(number[i] == 1){
                res += low + 1;
            }
            if(number[i] > 1){
                res += t;
            }
        }
        System.out.println(res);
        
        
    }
}

二进制问题

二进制问题

import java.util.Scanner;
import java.util.Arrays;
public class Main {
    static long n, K;
    static long[][] dp = new long[66][66];
    static int[] num = new int[66];
    /*
    len:表示当前正在处理的二进制数的位数,也就是二进制数的长度;
    sum:表示当前二进制数中已经出现的 1 的个数;
    limit:表示当前位是否有限制。如果为 true,则表示当前位上的数不能大于 num[len],否则可以随意取值。
    */
    static long dfs(int len, int sum, boolean limit){
      // 已经处理到了最后一位,则判断sum是否等于K,是则返回1,否则返回0
      if(len == 0){
        return sum == K ? 1: 0;
      }
      // 优化搜索:如果该状态已经被计算过且当前为没有限制
      if(dp[len][sum] != -1 && !limit){
        return dp[len][sum];
      }
      int up = limit ? num[len] : 1;  // 该位能否取1
      long res = 0;
      // 枚举当前位的所有可能取值
      for(int i = 0; i <= up; i ++){
        res += dfs(len - 1, sum + (i == 1 ?1 : 0), limit && i == up);
      }
      return dp[len][sum] = res;
      
    }

    static long solve(long n) {
      for (long[] longs : dp) Arrays.fill(longs, -1);
      int len = 0;
      // 分离位数:十进制转二进制,按低位到高位的顺序存储
      while (n != 0) {
        num[++len] = (int) (n & 1);
        n >>= 1;
      }
      return dfs(len, 0, true);
    }


    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        n = cin.nextLong();
        K = cin.nextLong();
        System.out.println(solve(n));
      
    }
}


##  异或三角形
[]()

```java
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    static int ans[] = new int[5];
    static long res = 0;
    //  求x以内能满足如下条件的三个数
    public static void dfs(int u, int cur, int x){
        //System.out.println(u + " "+ cur+" " + x);
        if(u == 3){
        if(ans[0] + ans[1] > ans[2]){
          long k = ans[0] ^ ans[1];
          if((k ^ ans[2]) == 0){
            res ++;
          }
        }
        return;
        }
        for(int i = cur; i <= x; i ++){
          ans[u] = i;
          dfs(u + 1, i, x);
        }
    }


    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int t = scan.nextInt();
        for(int i = 0; i < t; i ++){
            int a = scan.nextInt();
            long sum = 0;
            res = 0;
            for(int j = 1; j <= a; j ++){
                dfs(0, 1, j);
                sum += res;
            }
            System.out.println(sum * 6); // 六种排列
        }
        scan.close();
    }
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    ios::sync_with_stdio(false);
    int T;cin>>T;
    while(T--)
    {
        ll n;cin>>n;
        ll ans=0;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=n;j++)
            {
                ll k=i^j;  // 因为要求异或为0,故第三个数必为i与j异或的结果
                if(k<=n&&k>=1&&i+j>k&&i+k>j&&k+j>i)ans++;
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

Java异或运算

线性dp

费用问题

312. 乌龟棋
在这里插入代码片

线性dp


/*
问题实际等价于从N个数中选择M个数字,要求这M个数字之和要最大。且选择的数字有条件限制,即满足步数限制(b[i])。
f(a, b, c, d) 集合:定义走到终点卡片1使用a张,卡片2使用了b张……的所有方案
        属性:方案的最大值
格子, 卡片 



i - 1(判空)   
i - 2
i - 3
i - 4
i   1   2   3  …… i …… n
*/
import java.util.Scanner;

class Main{
    static int N = 355, M= 125;
    static int w[] =  new int[N];
    static int cnt[] = new int[5];
    static int f[][][][] = new int[45][45][45][45];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt(), m = reader.nextInt();
        for(int i = 1; i <= n; i ++){
            w[i] = reader.nextInt();
        }
        for(int i = 1; i <= m; i ++){
            cnt[reader.nextInt()] ++;
        }
        f[0][0][0][0] = w[1];
        for(int a = 0; a <= cnt[1]; a ++){
            for(int b = 0; b <= cnt[2]; b ++){
                for(int c = 0; c <= cnt[3]; c ++){
                    for(int d = 0; d <= cnt[4]; d ++){
                        int t = 1 + a + b * 2 + c * 3 + d * 4; // 加上初始的一步
                        if(a > 0){
                            f[a][b][c][d] = Math.max(f[a][b][c][d], f[a - 1][b][c][d] + w[t]);
                        }
                        if(b > 0){
                            f[a][b][c][d] = Math.max(f[a][b][c][d], f[a][b - 1][c][d] + w[t]);
                        }
                        if(c > 0){
                            f[a][b][c][d] = Math.max(f[a][b][c][d], f[a][b][c - 1][d] + w[t]);
                        }
                        if(d > 0){
                            f[a][b][c][d] = Math.max(f[a][b][c][d], f[a][b][c][d - 1] + w[t]);
                        }
                    }
                }
            }
        }
        System.out.println(f[cnt[1]][cnt[2]][cnt[3]][cnt[4]]);
    }
}

记忆化搜索

import java.util.Scanner;

public class Main {
    static final int N = 350 + 1;
    static final int M = 40 + 1;
    static int n, m, bi;
    static int[] a = new int[N];
    static int[] b = new int[5];
    static int[][][][] dp = new int[M][M][M][M];

    static int recurMemorized(int k, int b1, int b2, int b3, int b4) {
        if (dp[b1][b2][b3][b4] != 0) return dp[b1][b2][b3][b4];
        int maxNext = 0;
        if (b1 > 0) maxNext = Math.max(maxNext, recurMemorized(k + 1, b1 - 1, b2, b3, b4));
        if (b2 > 0) maxNext = Math.max(maxNext, recurMemorized(k + 2, b1, b2 - 1, b3, b4));
        if (b3 > 0) maxNext = Math.max(maxNext, recurMemorized(k + 3, b1, b2, b3 - 1, b4));
        if (b4 > 0) maxNext = Math.max(maxNext, recurMemorized(k + 4, b1, b2, b3, b4 - 1));
        return dp[b1][b2][b3][b4] = a[k] + maxNext;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        for (int i = 1; i <= n; ++i) a[i] = scanner.nextInt();
        for (int i = 1; i <= m; ++i) {
            bi = scanner.nextInt();
            ++b[bi];
        }
        System.out.println(recurMemorized(1, b[1], b[2], b[3], b[4]));
        scanner.close();
    }
}

1051. 最大的和

1051. 最大的和

最大子段和

 //  1 -1 2 2 3 -3 4 -4 5 -5
 //0 1  0 2 4 7  4 8  4 9  4 
 int n =  reader.nextInt();
 for(int i = 1; i <= n; i ++){
     a[i] = reader.nextInt();
 }
 int f[] = new int[N]; // 表示以第i个数字结尾的最大字段和
 f[0] = 0;
 for(int i = 1; i <= n; i++){
     for(int j = 1; j <= i; j ++){
         f[i] = Math.max(a[i], a[i] + f[i - 1]);
     }
 }

AcWing 898. 数字三角形

【题目描述】

AcWing 898. 数字三角形

自底向上的走法

由于到顶部出口是唯一确定的

【思路】
for(i 从n - 2到 0)
for(j 从0到i)
f[i][j] = max(f[i + 1][j],f[i + 1][j + 1]) +f[i][j]



import java.util.Scanner;
import java.lang.Math;
class Main{
    static int N = 510;
    static int f[][] = new int[N][N];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        for(int i = 0; i < n; i ++)
            for(int j = 0; j <= i; j ++)
                f[i][j] = reader.nextInt();
        
        for(int i = n - 2; i >= 0; i --)
            for(int j = 0; j <= i; j ++){
                f[i][j] = Math.max(f[i + 1][j],f[i + 1][j + 1]) +f[i][j];
            }
        System.out.println(f[0][0]);        
    }
}

自顶向下的写法

/**

自顶向下走:
    由于当前格子只可能是从左上角或右上角走过来 路径是唯一确定的,只需选择较大者
    便可确定一条从上走到当前位置的路径
    底部有多个出口,那么走到底部就有多条路径

 */
import java.util.Scanner;
import java.lang.Math;
class Main{
    static int N = 510;
    static int f[][] = new int[N][N];
    public static void main(String args[]){
        
        
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        
        //注意数据有负数 所以要初始化边界
        for(int i = 0; i < N; i ++)
            for(int j = 0; j < N; j ++)
                f[i][j] = Integer.MIN_VALUE;
                
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= i; j ++)
                f[i][j] = reader.nextInt();
        
        for(int i = 2; i <= n; i ++){
            for(int j = 1; j <= i; j ++){
                f[i][j] += Math.max(f[i - 1][ j - 1] , f[i - 1][j]);
            }
            
        }
        int res = Integer.MIN_VALUE;
        for(int i = 1; i <= n; i ++)
            if( f[n][i] > res ) res = f[n][i];
        System.out.println(res);        
    }
}

注意 :
从底部往顶部的走法,由于到顶部出口是唯一确定的,所以即是最大值。
但是从顶部走到底部有多个出口,所以还有for循环取最大值。

相似题
Acwing 1015. 摘花生

AcWing 895. 最长上升子序列

【题目描述】
AcWing 895. 最长上升子序列

【思路】

在这里插入图片描述
图源 Acwing 小呆呆

状态计算,根据倒数第二个数可以是什么(a[1]、a[2] ……a[k]、……a[i - 1])将集合划分为i - 1个子集。在所有子集中取max然后加上最后一个是a[i]的1这个长度即为f[i]。

7
a[i]:3 1 2 1 8 5 6
f [i]:1 1 2 1 3 3 4
f[i]表示以第i个数字结尾的序列中的最长递增子序列
初始化f数组为全1
for(int i = 1; i < n; i ++)
for(int j = 0; j < i; j ++)
if( a[i] > a[j]) f[i] = Math.max(f[j] + 1, f[i]);


import java.util.Scanner;
import java.lang.Math;
class Main{
    static int N = 1010;
    static int f[] = new int[N];
    static int a[] = new int[N];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        for(int i = 0; i < n; i ++ ) a[i] = reader.nextInt();
        for(int i = 0; i < n; i ++ ) f[i] = 1;
        
        for(int i = 1; i < n; i ++)
            for(int j = 0; j < i; j ++)
                if( a[i] > a[j]) f[i] = Math.max(f[j] + 1, f[i]);
        
        int ans = 0;
        for(int i = 0; i < n; i ++)
            if( f[i] > ans) ans = f[i];
        
        System.out.println(ans);
    }
}

AcWing 896. 最长上升子序列 II

【题目描述】

AcWing 896. 最长上升子序列 II

【思路】

以3 1 2 1 8 5 6为例,
长度是1的子序列集和中有 {3},{1}, 但是可以发现{3}是多余的,因为1比3更小,使用范围更大,更优。
受此启发,可以以长度来划分集合,属性值保存:长度为len的子序列结尾的最小值。

在这里插入图片描述
根据上升子序列的特点可以知道,q[len]一定是严格递增的。

1、从前往后枚举数组a[i],二分查找出q[len]中最后一个小于 a[i]的位置mid。a[i]即要接在mid之后,以a[i]结尾的上升子序列长度为mid + 1。同时一定有q[mid + 1] <= a[i],更新q[mid + 1]为a[i].

import java.util.Scanner;

public class Main{
    static int N = 100010;
    static int a[] = new int[N];
    static int q[] = new int[N];
    public static void main(String args[]){
        Scanner reader = new Scanner (System.in);
        int n = reader.nextInt();
        for(int i = 0; i < n; i ++) a[i] = reader.nextInt();
        int len = 0;
        q[0] = Integer.MIN_VALUE;
        for(int i = 0; i < n; i ++)
        {
            //查找最后一个小于a[i]的位置mid
            int l = 0, r = len;
            while(l < r){
                int mid = l + r + 1 >> 1;
                if( q[mid] < a[i]) l = mid;
                else r = mid - 1;
            }
            //更新长度为l + 1 上升子序列的结尾最小值为a[i]
            q[l + 1] = a[i];
            //更新len
            len = Math.max(len, l + 1);
        }
        System.out.println(len);
    }
}

AcWing 897. 最长公共子序列

【题目描述】
在这里插入图片描述

AcWing 897. 最长公共子序列

【思路】
在这里插入图片描述

import java.util.Scanner;
public class Main{
    static int N = 1010;
    static int f[][] = new int[N][N];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int m = reader.nextInt(), n = reader.nextInt();
        String A = reader.next(), B = reader.next();
        
        for(int i = 1; i <= m; i ++)
            for(int j = 1; j <= n; j ++)
            {
                f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);
                if( A.charAt(i - 1) == B.charAt(j - 1) ) 
                    f[i][j] = Math.max(f[i][j], f[i - 1][j - 1] + 1); 
            }
        System.out.println(f[m][n]);
    }
}

记忆化dp

AcWing 901. 滑雪

【题目描述】

AcWing 901. 滑雪

dfs暴力搜

过8/11

import java.util.Scanner;
public class Main{
    static int N = 310;
    static int R, C;
    static int f[][] = new int[N][N];
    static boolean st[][] = new boolean[N][N];
    static int ans = 0;
    static int dir[][] = { {-1, 0}, {0, 1}, {1, 0}, {0, - 1}};
    public static void dfs(int x, int y, int cur, int step){
        
        if( step > ans) ans = step;
        st[x][y] = true;
        for(int i = 0; i < 4; i ++)
        {
            int a = x + dir[i][0], b = y +dir[i][1];
            if( a < 0 || a >= R || b < 0 || b >= C) continue;
            if( st[a][b] ) continue;
            if( cur > f[a][b]){
                dfs(a, b, f[a][b], step + 1);
            }
            
        }
        st[x][y] = false;
    }
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        R = reader.nextInt();
        C = reader.nextInt();
        for(int i = 0; i < R; i ++)
            for(int j = 0; j < C; j ++)
                f[i][j] = reader.nextInt();
        
        //每一个起点

        for(int i = 0; i < R; i ++)
            for(int j = 0; j < C; j ++){
                dfs(i, j, f[i][j], 1);
            }
        System.out.println(ans);
        
    }
}

dp写法

【思路】
在这里插入图片描述

import java.util.Scanner;
import java.util.Arrays;
public class Main{
    static int N = 310;
    static int R, C;
    static int h[][] = new int[N][N];
    static int f[][] = new int[N][N];
    static int dir[][] = { {-1, 0}, {0, 1}, {1, 0}, {0, - 1}};
    
    public static int dfs(int x, int y){
        
        if( f[x][y] != -1) return f[x][y];
        
        //至少可以是该点自己
        f[x][y] = 1;
        for(int i = 0; i < 4; i ++)
        {
            int a = x + dir[i][0], b = y + dir[i][1];
            //四种滑法取最大值
            if( a >= 0 && a < R && b >= 0 && b < C && h[a][b] < h[x][y]){
                f[x][y] = Math.max(f[x][y] ,dfs(a, b) + 1);
            }
        }
        return f[x][y];
       
    }
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        R = reader.nextInt();
        C = reader.nextInt();
        for(int i = 0; i < R; i ++)
            for(int j = 0; j < C; j ++)
                h[i][j] = reader.nextInt();
        
        //每一个起点
         for(int i = 0; i < R; i ++) Arrays.fill(f[i], - 1);
        
        int ans = 0;
        for(int i = 0; i < R; i ++)
            for(int j = 0; j < C; j ++){
                ans = Math.max( ans, dfs(i, j) );
            }
        System.out.println(ans);
        
    }
}

备忘录写法【卡住了未完善】

import java.util.Scanner;
public class Main{
    static int N = 310;
    static int R, C;
    static int f[][] = new int[N][N];
    static boolean st[][] = new boolean[N][N];
    static int ans = 0;
    static int dir[][] = { {-1, 0}, {0, 1}, {1, 0}, {0, - 1}};
    static int memo[][][][] = new int[N][N][N][N];//四维备忘录 [当前位置][下一个位置]
    public static int dfs(int x, int y, int cur, int step){
        
        if(){
            
        }
        
        st[x][y] = true;
        for(int i = 0; i < 4; i ++)
        {
            int a = x + dir[i][0], b = y +dir[i][1];
            if( a < 0 || a >= R || b < 0 || b >= C) continue;
            if( st[a][b] ) continue;
            
            if( cur > f[a][b]){
                //搜索前先查找备忘录数据
                if(memo[x][y][a][b] != 0) return memo[x][y][a][b];
                else{
                    //查找后存储
                    memo[x][y][a][b] = dfs(a, b, f[a][b], step + 1);
                }
            
                
            }
            
        }
        st[x][y] = false;
        return ;
    }
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        R = reader.nextInt();
        C = reader.nextInt();
        for(int i = 0; i < R; i ++)
            for(int j = 0; j < C; j ++)
                f[i][j] = reader.nextInt();
        
        //每一个起点

        for(int i = 0; i < R; i ++)
            for(int j = 0; j < C; j ++){
                dfs(i, j, f[i][j], 1);
            }
        System.out.println(ans);
        
    }
}

区间dp

AcWing 282. 石子合并

【题目描述】

AcWing 282. 石子合并

【思路】
在这里插入图片描述

import java.util.Scanner;
public class Main{
    static int N = 310;
    static int s[] = new int[N];    //前缀和数组
    static int f[][] = new int[N][N]; //状态数组
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        for(int i = 1; i <= n; i ++){
            s[i] = reader.nextInt();
            s[i] += s[i - 1];
        }
        
        //枚举区间长度
        for(int len = 2; len <= n; len ++)
            //枚举左端点  len = 2   i = n - 1
            for(int i = 1; i + len - 1 <= n; i ++){
                //枚举区间右端点 
                int j = i + len - 1;
                f[i][j] = Integer.MAX_VALUE;
                for(int k = i; k < j; k ++){
                    f[i][j] = Math.min( f[i][k] + f[k + 1][j], f[i][j]);
                }
                f[i][j] += s[j] - s[i - 1];
                
            }
        System.out.println(f[1][n]);
        
    }
}

AcWing 3417. 砝码称重

在这里插入图片描述

import java.util.Scanner;
class Main{
    static int N = 110, M = 200010;// 正整数和负整数重量
    static int B = M /2;
    static int w[] = new int[N];
    static boolean f[][] = new boolean[N][M];
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        int m = 0;
        for(int i = 1; i <= n; i ++){
            w[i] = reader.nextInt();
            m += w[i];
        }
        f[0][B] = true; // f[0][0] = true;
        for(int i = 1; i <= n; i ++){
            for(int j = - m; j <= m; j ++){
                f[i][j + B] = f[i - 1][j + B]; // 不选第i个砝码
                if(j - w[i] >= -m) {
                    f[i][j + B] |= f[i - 1][j - w[i] + B];
                }
                if(j + w[i] <= m) {
                    f[i][j + B] |= f[i - 1][j + w[i] + B];
                }
            }
        }
        int ans = 0;
        for(int j = 1; j <= m; j ++){
            if(f[n][j + B]){
                ans ++;
            }
        }
        System.out.println(ans);
    }
}

树形dp

AcWing 285. 没有上司的舞会(算法基础课
在这里插入图片描述

在这里插入图片描述

/*
     树等价于有向图
*/
import java.util.Scanner;
import java.util.Arrays;
public class Main{

    static int N = 6010;
    static int happy[] = new int[N];
    static int h[] = new int[N];    // 邻接表(存储的是以u为根的相邻一层的所有子节点)
    static int e[] = new int[N];    // 节点数组,存储节点编号
    static int ne[] = new int[N];   // nxt数组,存储序号
    static boolean hasFather [] = new boolean[N];
    static int f[][] = new int[N][2];
    static int idx = 0;
    public static void add(int a, int b){
        e[idx] = b; // 新建节点
        ne[idx] = h[a];
        h[a] = idx;
        idx ++;
        
    }
    public static void dfs(int u){
        f[u][1] = happy[u];
        for(int i = h[u]; i != - 1; i = ne[i]){
            int j = e[i]; // 节点编号
            dfs(j);
            f[u][1] += f[j][0];
            f[u][0] += Math.max(f[j][1], f[j][0]);
        }
    }
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        for(int i = 1; i <= n; i ++){
            happy[i] = reader.nextInt();
        }
        Arrays.fill(h, - 1);    // 注意初始化的位置,链表头结点都初始化为-1
        for(int i = 0; i < n - 1; i ++){
            int a = reader.nextInt();
            int b = reader.nextInt(); //父
            add(b, a);
            hasFather[a] = true;
            
        }
        int root = 1;
        while(hasFather[root]){ root ++;}
        System.out.println(root);
        dfs(root);
        System.out.println(Math.max(f[root][0], f[root][1]));
        
        
        
    }
}

减肥


DFS暴力过2/5
/*
至少两周,则每周最多减重重量为[1, n/2]  // 0不是正整数
每一周要比上一周多

*/

import java.util.Scanner;
public class Main{
    static int ans = 0;
    static int n;
    public static void dfs(int k, int cur, int sum){
        if(sum > n){
            return;
        }
        if(sum == n){
            if(k >= 2){
                ans ++;
            }
            return;
        }
        for(int i = cur + 1; (i - 1) * 2 <= n; i ++){
            dfs(k + 1, i, sum + i);
        }
    }
    public static void main(String args[]){
        Scanner reader = new Scanner(System.in);
        n = reader.nextInt();
        for(int i = 1; i * 2 < n; i ++){ //第一周减肥重量
            dfs(1, i, i);
        }
        System.out.println(ans);
    }
}
/*
dp(i,j)表示i分解为j个正整数的方案集合
当前正整数为 j,即最后一个正整数是 j。那么我们需要在剩余的 i-j 中分解为 j-1 个正整数。
即dp[i-j][j-1]
当前正整数不是 j,即最后一个正整数小于 j。那么我们需要在剩余的 i 中分解为 j 个正整数。
即dp[i-1][j]

*/
import java.util.Scanner;
public class Main {

    public static int countWeightLossPlans(int n) {
        int[][] dp = new int[n + 1][n + 1];
        // dp[i][1] 表示将 i 分解为一个正整数的方案数,由于只有一种情况(即 i 本身),所以初始化为 1。
        for (int i = 1; i <= n; i++) { 
            dp[i][1] = 1;
        }
        
        for (int i = 1; i <= n; i++) {
            for (int j = 2; j <= i; j++) {
                dp[i][j] = dp[i - 1][j - 1] + dp[i - j][j];
            }
        }

        int count = 0;
        //  j 的范围是 2 到 n,因为至少需要两个正整数才能减重。
        for (int j = 2; j < n; j++) {
            count += dp[n][j];
        }

        return count;
    }

    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        int count = countWeightLossPlans(n);
        System.out.println(count);
    }
}


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
TSP问题(Traveling Salesman Problem,旅行商问题)是一个经典的组合优化问题,它要求在给定的城市之间找到一条最短路径,使得每个城市只被经过一次,并且最终回到起点。 在本文中,我们将介绍如何使用Python解决TSP问题的动态规划算法动态规划算法 动态规划算法是一种解决复杂问题的有效方法,它通常用于优化问题。TSP问题的动态规划算法的思路是:将问题分解为子问题,然后通过计算子问题的最优解来逐步构建整个问题的最优解。 具体来说,我们可以使用以下步骤来解决TSP问题: 1. 定义状态:将TSP问题定义为一个二元组$(S,i)$,其中$S$表示已经经过的城市集合,$i$表示当前所在的城市。 2. 定义状态转移方程:我们定义$dp(S,i)$表示从城市$i$出发,经过集合$S$中所有城市的最短路径长度。状态转移方程为: $$ dp(S,i) = \begin{cases} 0 & \text{if } S=\{i\} \\ \min\limits_{j\in S,j\ne i}\{dp(S-\{i\},j)+dist[j][i]\} & \text{otherwise} \end{cases} $$ 其中$dist[i][j]$表示城市$i$到城市$j$之间的距离。 3. 初始状态:$dp(\{i\},i)=0$。 4. 最终状态:$dp(\{1,2,\cdots,n\},1)$即为所求的最短路径长度。 代码实现 下面是使用Python实现TSP问题动态规划算法的代码: ```python import math def tsp_dp(dist): n = len(dist) # 记录子问题的最优解 dp = [[math.inf] * n for _ in range(1 << n)] # 初始状态 for i in range(n): dp[1 << i][i] = 0 # 构建状态转移方程 for s in range(1, 1 << n): for i in range(n): if s & (1 << i) == 0: continue for j in range(n): if i == j or s & (1 << j) == 0: continue dp[s][i] = min(dp[s][i], dp[s ^ (1 << i)][j] + dist[j][i]) # 返回最终状态 return min(dp[(1 << n) - 1][i] + dist[i][0] for i in range(n)) # 示例 dist = [ [0, 2, 9, 10], [1, 0, 6, 4], [15, 7, 0, 8], [6, 3, 12, 0] ] print(tsp_dp(dist)) # 输出:21 ``` 在上面的代码中,我们首先使用$dp$数组记录子问题的最优解,然后通过状态转移方程逐步构建整个问题的最优解。 最后,我们通过计算$dp(\{1,2,\cdots,n\},1)$和从最后一个城市回到起点的距离之和的最小值来得到TSP问题的最优解。 总结 通过本文,我们学习了如何使用Python解决TSP问题的动态规划算法。TSP问题是一个经典的组合优化问题,它的解决方法还有很多其他的算法,例如分支定界算法、遗传算法等。如果你对这些算法感兴趣,可以进一步学习相关的知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值