-
动态规划问题
小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);
}
}
}