201312-4 有趣的数:
动态规划:每一步由上一步的最优解决定,大事化小
要点:找出状态位,找出边界值
当0~3 一位一位 的按序 填数字,0在1前面,2在3前面,0不能为最高位时,最多有6种状态:
0状态:2 – 还剩 0、1、3
1状态:2 0 – 还剩 1、3
2状态:2 3 – 还剩 0、1
3状态:2 0 1 – 还剩 3
4状态:2 3 0 – 还剩 1
5状态:4位数全填 – 不剩
建立一个二维表t[n][6],行代表所求位数[i],列代表状态位[j]
t[i]取决于t[i-1],即在t[i-1][j]的基础上加1位数字
要达到i位数的0状态,即t[i][0],则是从t[i-1][0]状态得到(一直是1)
要达到i位数的1状态,从t[i-1][0](填0)和t[i-1][1](填2或0)
要达到i位数的2状态,即 t[i][2],则可从t[i-1][0](填3)和t[i-1][2](填3)得到
要达到i位数的3状态,即t[i][3],则可从t[i-1][1](填1)和t[i-1][3](填2或1)得到
要达到i位数的4状态,则可从t[i-1][1](填3)t[i-1][2](填0)和t[i-1][4](填3或0)得到
要达到i位数的5状态,则可从t[i-1][3](填3)t[i-1][4](填1)和t[i-1][5](填3或1)得到
t[i][5]就是求的恰好有i位的有趣数的个数(i>=4)
由于数值较大,可以每次计算状态位都取模
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
long mod = 1000000007;
int n = 4;
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
long[][] t = new long[n+1][6];
for(int i=1; i<=n; i++){
int j = i-1;
t[i][0] = 1;
t[i][1] = (t[j][0] + t[j][1]*2)%mod;
t[i][2] = (t[j][0] + t[j][2])%mod;
t[i][3] = (t[j][1] + t[j][3]*2)%mod;
t[i][4] = (t[j][1] + t[j][2] + t[j][4]*2)%mod;
t[i][5] = (t[j][3] + t[j][4] + t[j][5]*2)%mod;
}
System.out.println(t[n][5]);
}
}
最简单的动态规划问题:
台阶问题:每次只能上1级或者2级台阶,到第n级台阶共有多少种方法
思路:f(n) = f(n-1)+f(n-2) 其中n>2,即上第n级台阶的方法,是上n-1和n-2级台阶方法之和
f(1)和f(2)为边界
时间复杂度O(N),空间复杂度O(1)
public int f1(int n){
int x1 = 1, x2 = 2;
int temp = 0;
if(n < 1){
return 0;
}
if(n == 1){ //1级台阶,1种上法
return x1;
}
if(n == 2){ //2级台阶,2种上法
return x2;
}
for(int i=3; i<n; i++){
temp = x2;
x2 += x1;
x1 = temp;
}
return x1+x2;
}
进阶问题:
金矿问题:有n个工人,m座矿山,每座矿山产量G[m],需要人力P[m],求最大总产量
思路:某一座矿山是否开采,取决于前面的矿山
建立一个二维表t,行是矿山数m,列是工人数n,值是总产量
状态位:
人数未使用(n < min(P[m])):人数不足新矿山:0;人数足够新矿山:G[i];
人数不足:t[m][n] = max(t[m-1][n-P[m]]+G[M], t[m-1][n])
要考虑 n-P[m]下标溢出(=-1)的情况
人数足够:t[m][n] = t[m-1][n-P[m]]+G[M]
只有一座矿山时的产量即为边界
public int[][] f2(int n, int m, int[]G, int[]P){
int[][] t = new int[m][n]; //记录m座矿山,n个工人时的最大产量
for(int j=0; j<n; j++){ //初始化第一行
if(j+1 >= P[0]){ //完全初始化第一行,注意工人数从0开始,0即1个工人
t[0][j] = G[0];
}
}
for(int i=1; i<m; i++){//外层矿山,内层工人
for(int j=0; j<n; j++){
int[] Pm = Arrays.copyOfRange(P, 0, i+1); //复制数组
if(t[i-1][j] == 0 && j+1 >= P[i]){ //当前工人未使用。够新矿山所需人数
t[i][j] = G[i];
}
else if(j+1 == P[i-1] && G[i] > G[i-1] && j+1 == P[i]){ //当前工人已使用。新矿山在相同人数下产量更高
t[i][j] = G[i];
}
else if(j+1 < sum(P, i) && j+1 >= min(Pm) && j>=P[i]){ //人数不足,且能算入新矿山
t[i][j] = Math.max(t[i-1][j-P[i]]+G[i], t[i-1][j]);
}
else if(j+1 < sum(P, i) && j+1 >= min(Pm) && j+1>=P[i]){ //人数不足,全放新矿山刚好(放到上一个判断,下标会溢出)
t[i][j] = G[i] > t[i-1][j]? G[i]:t[i-1][j];
}
else if(j+1 >= sum(P, i)){ //如果人数充足,则产量直接相加
t[i][j] = t[i-1][j] + G[i];
}
else{ // 当前工人已使用,新矿山相同人数下产量更低
t[i][j] = t[i-1][j];
}
}
}
return t;
}
public int min(int[]P){ //求已有矿山中所需最少工人数
Arrays.sort(P); //升序排序
return P[0];
}
public int sum(int[]P, int end){ //求已有矿山共需多少工人
int sum = 0;
for(int i=0; i<=end; i++){
sum += P[i];
}
return sum;
}