練習題若干

  • 動態規劃問題

小Q和牛博士合唱一首歌曲,這首歌曲由n個音調組成,每個音調由一個正整數表示。

對於每個音調要麼由小Q演唱要麼由牛博士演唱,對於一系列音調演唱的難度等於所有相鄰音調變化幅度之和, 例如一個音調序列是8, 8, 13, 12, 那麼它的難度等於|8 - 8| + |13 - 8| + |12 - 13| = 6(其中||表示絕對值)。

現在要對把這n個音調分配給小Q或牛博士,讓他們演唱的難度之和最小,請你算算最小的難度和是多少。

如樣例所示: 小Q選擇演唱{5, 6}難度爲1, 牛博士選擇演唱{1, 2, 1}難度爲2,難度之和爲3,這一個是最小難度和的方案了。

解:

  • dp[i][j](永遠有i > j)表示某一個人最近唱的音爲第i個,另一個人最近唱的是第j個時最小的難度
  • 由於只由一個人唱完肯定不是最優解。因此先在一個for循環內確定以下兩種情況的初值

dp[i][0]:第二個人唱第一個音,第一個人唱後面所有音

dp[i][i-1]:第一個人唱最近的一個音,第二個人唱前面所有音

  • dp[i][j]轉移方程

每當交換唱歌次序,兩人最近一次唱的音符一定是相鄰的,所以底下分相鄰和不相鄰討論:
(1). 當j == i - 1,即交換唱歌次序,dp[i][i-1]時,新唱第i個音的人上一個音可能在第k個音(0 <= k < i-1),另一個人仍然留在第i-1個音。有:
dp[i][i-1] = 對所有k求min(dp[i-1][k] + abs(arr[i] - arr[k]) ) 其中(0 <= k < i-1)
(2). 當j < i - 1,i-1與i爲同一個人唱,有:
dp[i][j] = dp[i-1][j] + abs(arr[i] - arr[i-1])

最後求出所有dp[len-1][]裏的最小值即爲全局最優解

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNext()) {
            int len = in.nextInt();
            int[] arr = new int[len];
            for (int i = 0; i < len; ++i) {
                arr[i] = in.nextInt();
            }
            if (len < 3) {
                System.out.println("0");
            } else {
                int[][] dp = new int[len][len];
                int[] acc = new int[len];
                //保證dp[1][0]爲0,即只有2個音符時肯定爲0
                dp[0][0] = 0 - Math.abs(arr[1] - arr[0]);
                for (int i = 1; i < len; ++i) {
                    //前i+1個由同一個人唱的難度
                    acc[i] = acc[i - 1] + Math.abs(arr[i] - arr[i - 1]);
                    //初始值:一個人唱i+1,另一個唱前面所有的。難度基本肯定不是最小的,所以在後面循環裏進行最小比較。
                    dp[i][i - 1] = acc[i - 1];
                    for (int j = 0; j < i - 1; ++j) {
                        dp[i][j] = dp[i - 1][j] + acc[i] - acc[i - 1];
                        dp[i][i - 1] = Math.min(dp[i][i - 1], dp[i - 1][j] + Math.abs(arr[i] - arr[j]));
                    }
                }
                int min = Integer.MAX_VALUE;
                for (int j = 0; j < len - 1; ++j) {
                    min = Math.min(min, dp[len - 1][j]);
                }
                System.out.println(min);
            }
        }
    }
}

 



  • LCS變例

一個合法的括號匹配序列被定義爲:
1. 空串""是合法的括號序列
2. 如果"X"和"Y"是合法的序列,那麼"XY"也是一個合法的括號序列
3. 如果"X"是一個合法的序列,那麼"(X)"也是一個合法的括號序列
4. 每個合法的括號序列都可以由上面的規則生成
例如"", "()", "()()()", "(()())", "(((()))"都是合法的。
從一個字符串S中移除零個或者多個字符得到的序列稱爲S的子序列。
例如"abcde"的子序列有"abe","","abcde"等。
定義LCS(S,T)爲字符串S和字符串T最長公共子序列的長度,即一個最長的序列W既是S的子序列也是T的子序列的長度。
小易給出一個合法的括號匹配序列s,小易希望你能找出具有以下特徵的括號序列t:
1、t跟s不同,但是長度相同
2、t也是一個合法的括號匹配序列
3、LCS(s, t)是滿足上述兩個條件的t中最大的
因爲這樣的t可能存在多個,小易需要你計算出滿足條件的t有多少個。

如樣例所示: s = "(())()",跟字符串s長度相同的合法括號匹配序列有:
"()(())", "((()))", "()()()", "(()())",其中LCS( "(())()", "()(())" )爲4,其他三個都爲5,所以輸出3.

解:

根據題意,要想使得 LCS 最大,刪去任意一個字符即可獲得 LCS = |s| - 1 ,再把該字符插到與原來不同的任意位置可以維持原長度,而不影響 LCS 的計算。



import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class Main {

static int fun(String s){
	Set<String> set=new HashSet<>();
	StringBuilder sb=new StringBuilder();
	for(int i=0;i<s.length();i++){
		sb.delete(0, sb.length());
		sb.append(s);
		sb.deleteCharAt(i);
		for(int j=0;j<s.length();j++){
			//比如同是左括號,我插在前面與插在後面是一樣的
			if(s.charAt(i)==s.charAt(j))continue;
			String temp=sb.insert(j, s.charAt(i)).toString();
			if(test(temp))
				set.add(temp);
			sb.deleteCharAt(j);
		}
	}
	return set.size();
}
static boolean test(String s){
	int cnt=0;
	for(int i=0;i<s.length();i++)
	{
		cnt=s.charAt(i)=='('?++cnt:--cnt;
		if(cnt<0)
			return false;
	}
	return true;
}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		String s=sc.next();
		sc.close();
		int n=fun(s);
		System.out.println(n);

	
	}
}


  • BFS問題

給定一個 n 行 m 列的地牢,其中 '.' 表示可以通行的位置,'X' 表示不可通行的障礙,牛牛從 (x0 , y0 ) 位置出發,遍歷這個地牢,和一般的遊戲所不同的是,他每一步只能按照一些指定的步長遍歷地牢,要求每一步都不可以超過地牢的邊界,也不能到達障礙上。地牢的出口可能在任意某個可以通行的位置上。牛牛想知道最壞情況下,他需要多少步纔可以離開這個地牢。

輸入描述:

每個輸入包含 1 個測試用例。每個測試用例的第一行包含兩個整數 n 和 m(1 <= n, m <= 50),表示地牢的長和寬。接下來的 n 行,每行 m 個字符,描述地牢,地牢將至少包含兩個 '.'。接下來的一行,包含兩個整數 x0, y0,表示牛牛的出發位置(0 <= x0 < n, 0 <= y0 < m,左上角的座標爲 (0, 0),出發位置一定是 '.')。之後的一行包含一個整數 k(0 < k <= 50)表示牛牛合法的步長數,接下來的 k 行,每行兩個整數 dx, dy 表示每次可選擇移動的行和列步長(-50 <= dx, dy <= 50)

輸出描述:

輸出一行一個數字表示最壞情況下需要多少次移動可以離開地牢,如果永遠無法離開,輸出 -1。以下測試用例中,牛牛可以上下左右移動,在所有可通行的位置.上,地牢出口如果被設置在右下角,牛牛想離開需要移動的次數最多,爲3次。

如:輸入

3 3
...
...
...
0 1
4
1 0
0 1
-1 0
0 -1

輸出:3



import java.util.*;
 
public class Main {
    public static void main(String[] args){
          Scanner in = new Scanner(System.in);
             
            while (in.hasNext()) {//注意while處理多個case
                  int x=in.nextInt();
                  int y=in.nextInt();
                   
                  char[][] points=new char[x][y];
                  int[][] tar=new int[x][y];
                  for(int i=0;i<x;i++){
                      String str=in.next();
                      points[i]=str.toCharArray();
                  }
                  int startx=in.nextInt();
                  int starty=in.nextInt();
                  int k=in.nextInt();
                  int[] stepx=new int[k];
                  int[] stepy=new int[k];
                  for(int i=0;i<k;i++){
                      stepx[i]=in.nextInt();
                      stepy[i]=in.nextInt();
                  }
                  Queue<Integer> xqueue=new LinkedList<Integer>();
                  Queue<Integer> yqueue=new LinkedList<Integer>();  
                  //引入隊列是爲了遍歷到最後不能走爲止
                   
                  xqueue.add(startx);
                  yqueue.add(starty);
                   
                  tar[startx][starty]=1;  //起始點訪問標記;1表示已經訪問
                  while(!xqueue.isEmpty()&&!yqueue.isEmpty()){
                      startx=xqueue.remove();    //取隊首
                      starty=yqueue.remove();
                       for(int i=0;i<k;i++){
                          if(startx+stepx[i]<x&&startx+stepx[i]>=0&&starty+stepy[i]<y&&starty+stepy[i]>=0)   //不出界
                          if(tar[startx+stepx[i]][starty+stepy[i]]==0){//沒有訪問
                              if(points[startx+stepx[i]][starty+stepy[i]]=='.'){//可以通行
                                  tar[startx+stepx[i]][starty+stepy[i]]=tar[startx][starty]+1;//step加1
                                  xqueue.add(startx+stepx[i]);//可以走的都入隊
                                  yqueue.add(starty+stepy[i]);
                              }
                              else
                                  tar[startx+stepx[i]][starty+stepy[i]]=-1;  //訪問點爲X
                          }
                      }
                  }
                  int max=0;
                  int getRoad=1;  
                  for(int i=0;i<x;i++)
                      for(int j=0;j<y;j++){
                          if(points[i][j]=='.'&&tar[i][j]==0){
                              getRoad=0;   //如果存在“.”,但卻不能到達,說明此時是最壞情況,輸出-1
                          }
                          max=Math.max(max, tar[i][j]);
                      }
                  if(getRoad==0)
                      System.out.println(-1);
                  else
                      System.out.println(max-1);//tar原點初始值設爲1了
                   
               }
       }
}
  • 打土豪

牛牛和 15 個朋友來玩打土豪分田地的遊戲,牛牛決定讓你來分田地,地主的田地可以看成是一個矩形,每個位置有一個價值。分割田地的方法是橫豎各切三刀,分成 16 份,作爲領導幹部,牛牛總是會選擇其中總價值最小的一份田地, 作爲牛牛最好的朋友,你希望牛牛取得的田地的價值和儘可能大,你知道這個值最大可以是多少嗎?

輸入:每個輸入包含 1 個測試用例。每個測試用例的第一行包含兩個整數 n 和 m(1 <= n, m <= 75),表示田地的大小,接下來的 n 行,每行包含 m 個 0-9 之間的數字,表示每塊位置的價值。比如:

4 4
3332
3233
3332
2323

輸出:輸出一行表示牛牛所能取得的最大的價值。輸出2

解題思路:使用2分法查找,在總和和0之間慢慢的去試探,試探到最後肯定就是最大的那個值。




import java.util.*;
 
public class Main {
	static boolean judge(int x,int n,int m,int[][] sum){
		for(int i=1;i<=m-3;i++){//ijk表示豎切三刀,從第一列之後到最後一列之前
			for(int j=i+1;j<=m-2;j++){
				for(int k=j+1;k<=m-1;k++){
					int cnt=0;
					int start=0;
					for(int r=1;r<=n;r++){//橫切,第一行之後開始
						int s1=g(start,0,r,i,sum);
						int s2=g(start,i,r,j,sum);
						int s3=g(start,j,r,k,sum);
						int s4=g(start,k,r,m,sum);
						if(s1>=x&&s2>=x&&s3>=x&&s4>=x){
							start=r;
							cnt++;
						}
						if(cnt>=4){
							return true;
						}
					}
				}
			}
		}
		return false;
	}
    private static int g(int x1, int y1, int x2, int y2, int[][] sum) {
		return sum[x2][y2]-sum[x1][y2]-sum[x2][y1]+sum[x1][y1];
	}
	public static void main(String[] args){
          Scanner sc = new Scanner(System.in);
           
            while (sc.hasNextInt()) {//注意while處理多個case
                int n=sc.nextInt();
                int m=sc.nextInt();
                if(n<4||m<4){//如果不夠切6刀,一塊地也拿不到
                	System.out.println(0);
                	for(int i=0;i<n;i++)
                		sc.next();
                	continue;
                }
                int[][] a=new int[n+1][m+1];
                int[][] sum=new int[n+1][m+1];
                for(int i=1;i<=n;i++){
                	char[] s=sc.next().toCharArray();
                	for(int j=1;j<=m;j++){
                		a[i][j]=Integer.parseInt(s[j-1]+"");
                		sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
                		}
                }
                int left=0;int right=sum[n][m];
                int mid=-1,result=-1;
                while(left<=right){
                	mid=(left+right)/2;
                	if(judge(mid,n,m,sum)){
                		left=mid+1;
                		result=mid;
                	}
                	else{
                		right=mid-1;
                	}
                }
                System.out.println(result);
               }
                }
           
       }
  • 混合顏料

你就是一個畫家!你現在想繪製一幅畫,但是你現在沒有足夠顏色的顏料。爲了讓問題簡單,我們用正整數表示不同顏色的顏料。你知道這幅畫需要的n種顏色的顏料,你現在可以去商店購買一些顏料,但是商店不能保證能供應所有顏色的顏料,所以你需要自己混合一些顏料。混合兩種不一樣的顏色A和顏色B顏料可以產生(A XOR B)這種顏色的顏料(新產生的顏料也可以用作繼續混合產生新的顏色,XOR表示異或操作)。本着勤儉節約的精神,你想購買更少的顏料就滿足要求,所以兼職程序員的你需要編程來計算出最少需要購買幾種顏色的顏料?

輸入:第一行爲繪製這幅畫需要的顏色種數n (1 ≤ n ≤ 50) 第二行爲n個數xi(1 ≤ xi ≤ 1,000,000,000),表示需要的各種顏料.

輸出:輸出最少需要在商店購買的顏料顏色種數,注意可能購買的顏色不一定會使用在畫中,只是爲了產生新的顏色。

如:

3

1 7 3

輸出爲3

解題思路:排序+置換。每次排序後,如果異或結果更小,用更小的代替,比如4^5=1 那麼4的位置用1代替,後面1和自己異或爲0,這樣每個不需要的顏色都會變成0.



import java.util.*;

public class Main {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		while (sc.hasNextInt()) {// 注意while處理多個case
		int n=sc.nextInt();
		long[] a=new long[n];
		for(int i=0;i<n;i++)
			a[i]=sc.nextLong();
		for(int i=n-1;i>0;i--){
			Arrays.sort(a, 0, i+1);
			for(int j=i-1;j>=0;j--){
				if((a[j]^a[i])<a[j])
					a[j]=a[i]^a[j];
			}
		}
		int t;
		for(t=0;a[t]==0;t++);
		System.out.println(n-t);
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章