注意:以下部分内容摘自Acwing,仅用于个人学习记录,不做任何商业用途。
(一)状态压缩DP
(1)哈密顿路径(旅行商问题)
题目:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
public class Main {
static final int M=10000010;
static final int N=22;
//static int[][] f=new int[M][N]; //f[i][j]表示从状态i时走到j所花费 的路程
//static int[][] w=new int[N][N];
public static void main(String[] args) throws IOException {
//输入部分
BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
int n;
n=Integer.parseInt(br.readLine().trim());
int[][] f=new int[1<<n][n];
int[][] w=new int [n][n];
for(int i=0;i<n;i++) {
String[] s=br.readLine().split(" ");
for(int j=0;j<n;j++) {
w[i][j]=Integer.parseInt(s[j]);
}
}
//for(int i=0;i<n;i++)Arrays.fill(f[i], 0x3f);//因为要求最小值,所以初始化为无穷大
for (int i = 0; i < (1 << n); ++i)
for (int j = 0; j < n; ++j)
f[i][j] = Integer.MAX_VALUE >>> 1;//>>> 是一个位无符号右移操作符
f[1][0]=0;//因为零是起点,所以f[1][0]=0;
for(int i=0;i<(1<<n);i++) { //枚举所有状态
for(int j=0;j<n;j++) { //表示这个状态中的某个点(即走到哪个点了)
if((i>>j&1)==1) {
for(int k=0;k<n;k++) {//枚举路径上所有可能的、走到节点j之前的一个节点
//从点0走到点k的路径 = 路径i除去点j(i - (1 << j))
//且必须包含点k(... >> k & 1)的路径
if((i-(1<<j)>>k&1)==1) {
f[i][j]=Math.min(f[i][j], f[i-(1<<j)][k]+w[k][j]);
}
}
}
}
}
System.out.print(f[(1<<n)-1][n-1]);
}
}
(2)毕业旅行问题
该题与(1)不同的是,终点需要回到起点,该题可以通过(1)转化一下
起点变为1 ~ n - 1号点,终点变为0号点
这一步是将1 ~ n - 1号点作为起点,值为0号点到这些点的距离
for(int i = 1; i < n; i++) dp[b[i]][i] = w[0][i]; // 将1 ~ n - 1号点作为起点,值为0号点到这些点的距离
#include <iostream>
#include <cstring>
using namespace std;
const int N = 20, M = (1 << N) + 10;
int dp[M][N], n, w[N+1][N+1];
int b[N];
int main() {
cin >> n;
int final = 1 << n;
for(int i = 0; i < n; i++) b[i] = 1 << i;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
cin >> w[i][j];
memset(dp, 0x3f, sizeof(dp));
for(int i = 1; i < n; i++) dp[b[i]][i] = w[0][i]; // 将1 ~ n - 1号点作为起点,值为0号点到这些点的距离
for(int st = 0; st < final; st++) {
if(st & 1 && st != final - 1) continue; // 如果0号点被走过但又不是最终状态
for(int i = 0; i < n; i++) {
if(!(st >> i & 1)) continue; // 如果i号点没走过
for(int j = 0; j < n; j++)
if((st - b[i]) >> j & 1) dp[st][i] = min(dp[st - b[i]][j] + w[j][i], dp[st][i]);
}
}
cout << dp[final - 1][0]; // 走过所有点,终点是0号点
return 0;
}
作者:Amonologue
链接:https://www.acwing.com/solution/content/72826/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
(3)蒙德里安的梦想
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Vector;
public class Main {
//static int[][] f=new int[M][N]; //f[i][j]表示从状态i时走到j所花费 的路程
//static int[][] w=new int[N][N];
public static void main(String[] args) throws IOException {
//输入部分
BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
while(true) {
int n,m;
String[] s=br.readLine().split(" ");
n=Integer.parseInt(s[0]);
m=Integer.parseInt(s[1]);
if(n==0&&m==0)break;
long[][] f=new long[m+10][1<<n];
boolean[] st=new boolean[1<<n];
ArrayList<ArrayList<Integer>> state = new ArrayList<>();
for (int i = 0; i < 1<<n; i++) {
state.add(new ArrayList<>());
}
//第一部分:预处理1
//对于每种状态,先预处理每列不能有奇数个连续的0
for(int i = 0; i < (1 << n); i ++) {
int cnt = 0 ;//记录连续的0的个数
boolean isValid = true; // 某种状态没有奇数个连续的0则标记为true
for(int j = 0; j < n; j ++) { //遍历这一列,从上到下
if ( ((i >> j) & 1)==1) {
//i >> j位运算,表示i(i在此处是一种状态)的二进制数的第j位;
// &1为判断该位是否为1,如果为1进入if
if ((cnt & 1)==1) {
//这一位为1,看前面连续的0的个数,如果是奇数(cnt &1为真)则该状态不合法
isValid =false; break;
}
cnt = 0; // 既然该位是1,并且前面不是奇数个0(经过上面的if判断),计数器清零。
//其实清不清零没有影响
}
else cnt ++; //否则的话该位还是0,则统计连续0的计数器++。
}
if ((cnt & 1)==1) isValid = false; //最下面的那一段判断一下连续的0的个数
st[i] = isValid; //状态i是否有奇数个连续的0的情况,输入到数组st中
}
//第二部分:预处理2
// 经过上面每种状态 连续0的判断,已经筛掉一些状态。
//下面来看进一步的判断:看第i-2列伸出来的和第i-1列伸出去的是否冲突
for (int j = 0; j < (1 << n); j ++) { //对于第i列的所有状态
state.get(j).clear(); //清空上次操作遗留的状态,防止影响本次状态。
for (int k = 0; k < (1 << n); k ++) { //对于第i-1列所有状态
if ((j & k ) == 0 && st[ j | k])
// 第i-2列伸出来的 和第i-1列伸出来的不冲突(不在同一行)
//解释一下st[j | k]
//先判断第i-1列中的1个数是否合法
//再判断第i列中的1位置是否与i-1列冲突
//if(st[k|j]&&(j&k)==0)
//st[k|j]解释:第i-1列中1的总个数
//来源两部分:
//st[k]表明第i-2列插入到i-1列的1的个数,i-1列被动生成的1
//st[j]表明第i-1列插入到i列的1的个数,也就是i-1列自己生成的1
state.get(j).add(k);
//二维数组state[j]表示第j行,
//j表示 第i列“真正”可行的状态,
//如果第i-1列的状态k和j不冲突则压入state数组中的第j行。
//“真正”可行是指:既没有前后两列伸进伸出的冲突;又没有连续奇数个0。
}
}
for(int i=0;i<n;i++)Arrays.fill(f[i], 0);
//全部初始化为0,因为是连续读入,这里是一个清空操作。
//类似上面的state[j].clear()
f[0][0] = 1 ;// 这里需要回忆状态表示的定义
//按定义这里是:前第-1列都摆好,且从-1列到第0列伸出来的状态为0的方案数。
//首先,这里没有-1列,最少也是0列。
//其次,没有伸出来,即没有横着摆的。即这里第0列只有竖着摆这1种状态。
for (int i = 1; i <= m; i ++) { //遍历每一列:第i列合法范围是(0~m-1列)
for (int j = 0; j < (1<<n); j ++) { //遍历当前列(第i列)所有状态j
for (int r=0;r<state.get(j).size();r++) { // 遍历第i-1列的状态k,如果“真正”可行,就转移
int k=state.get(j).get(r);
f[i][j] += f[i-1][k]; // 当前列的方案数就等于之前的第i-1列所有状态k的累加。
}
}
}
System.out.println(f[m][0]);
}
}
}
(二)线性DP
乌龟棋(Acwing 321)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
public class Main {
public static void main(String ...args) throws IOException {new Main().run();}
int[][][][] f=new int [41][41][41][41];
int[] b=new int[10];
void run() throws IOException {
BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw=new PrintWriter(new OutputStreamWriter(System.out));
String[] s=br.readLine().split(" ");
int n=Integer.parseInt(s[0]);
int m=Integer.parseInt(s[1]);
int[] a=new int[n+10];
s=br.readLine().split(" ");
for(int i=0;i<n;i++) {
a[i]=Integer.parseInt(s[i]);
}
s=br.readLine().split(" ");
for(int i=1;i<=m;i++) {
int x=Integer.parseInt(s[i-1]);
b[x]++;
}
f[0][0][0][0]=a[0];
for (int A = 0; A <= b[1]; A ++ )
for (int B = 0; B <= b[2]; B ++ )
for (int C = 0; C <= b[3]; C ++ )
for (int D = 0; D <= b[4]; D ++ )
{
int t = a[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] + t);
if (B!=0) f[A][B][C][D] = Math.max(f[A][B][C][D], f[A][B - 1][C][D] + t);
if (C!=0) f[A][B][C][D] = Math.max(f[A][B][C][D], f[A][B][C - 1][D] + t);
if (D!=0) f[A][B][C][D] = Math.max(f[A][B][C][D], f[A][B][C][D - 1] + t);
}
pw.print(f[b[1]][b[2]][b[3]][b[4]]);
pw.close();
}
}