Java蓝桥杯算法(七)

注意:以下部分内容摘自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();
    	   
       }
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值