主要由例题引入
重点: 最优子结构,可划分成部分(有点像分治)
1. luogu P1220 关路灯
1 ## 区间DP:luogu :P1220 2 3 #include<cstdio> 4 #include<cstring> 5 #include<algorithm> 6 #define MAX 555 7 int n,c; 8 int dp[MAX][MAX][2]; 9 //dp[i][j][0/1]表示老张关完i ~~ j的灯时所在的位置 10 int sum[MAX]/*前缀和:用来计算功率*/,a[MAX],b[MAX];/*a是位置,b是功率*/ 11 12 int min(int a, int b) { 13 return a >= b ? b : a;//提高效率 14 } 15 16 17 int main() { 18 scanf("%d%d",&n,&c); 19 memset(dp,88,sizeof(dp)); 20 for(int i = 1; i <= n; i++) { 21 scanf("%d%d",&a[i],&b[i]); 22 sum[i] =sum[i - 1] + b[i]; 23 } 24 // printf("%d",sum[2]); 25 dp[c][c][0] = dp[c][c][1] = 0;//老张直接将自己所在的灯关掉,无时间,所以无功 26 for(int l = 2; l <= n; l++) {// 枚举现在的灯 27 for(int i = 1; i + l - 1 <= n; i++) { //不能越界 28 int j = i + l - 1;// 时间∨ 29 dp[i][j][0] = min(dp[i + 1][j][0] + (a[i + 1] - a[i]) * (sum[n] - sum[j] + sum[i]),//包括了i这一点的电能,因为走过来的过程中灯i也会耗电 30 dp[i + 1][j][1] + (a[j] - a[i]) * (sum[n] - sum[j] + sum[i])) ; 31 dp[i][j][1] = min(dp[i][j - 1][1] + (a[j] - a[j - 1]) * (sum[n] - sum[j - 1] + sum[i - 1]),//已经关了[i,j - 1] 注意:i关了,j还没关 所以j会耗电,i不会 32 dp[i][j - 1][0] + (a[j] - a[i]) * (sum[n] - sum[j - 1] + sum[i - 1])); 33 34 //dp[i][j][0] ---- i + 1 dp[i][j][1] ---- j - 1 他们对应的都是上一个状态 35 } 36 } 37 int ans = min(dp[1][n][0] , dp[1][n][1]); 38 printf("%d",ans); 39 }
2. luogu P1880 [NOI1995]石子合并
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define MAXN 200+9 5 #define INF 2147000047 6 7 int fmax[MAXN][MAXN], fmin[MAXN][MAXN],sum[MAXN]; 8 int lenth,n,ans1,ans2,a[MAXN]; 9 10 void init() {//将1~n的圈改成1~2n链 11 scanf("%d",&n); 12 lenth = n*2; 13 for(int i = 1; i <= n; i++) scanf("%d",&a[i]), a[i+n] = a[i]; 14 for(int i = 1; i <= lenth; i++) { 15 sum[i] = sum[i-1] + a[i]; 16 fmax[i][i] = 0; fmin[i][i] = 0;//初始化边界 17 } 18 } 19 20 void dp() { 21 for(int L = 2; L <= n; L++) //以合并的堆数为阶段(大堆由小堆决定 22 for(int i = 1; i <= lenth-L+1; i++) { //合并的起始位置 23 int j = i+L-1;//计算出合并的结束位置 24 fmax[i][j] = 0, fmin[i][j] = INF; 25 for(int k = i; k < j; k++) { // 决策 (因为后面有k+1,所以k要<j 26 fmax[i][j] = max(fmax[i][j], fmax[i][k]+fmax[k+1][j]+(sum[j]-sum[i-1])); 27 fmin[i][j] = min(fmin[i][j], fmin[i][k]+fmin[k+1][j]+(sum[j]-sum[i-1])); 28 //这的“(sum[j]-sum[i-1])” 也可以加在for(k)的外面 29 } 30 } 31 32 ans1 = 0, ans2 = INF; 33 for(int i = 1; i <= n; i++) {//以DP起点位置判断出最值 34 ans1 = max(ans1, fmax[i][i+n-1]); 35 ans2 = min(ans2, fmin[i][i+n-1]); 36 } 37 } 38 39 int main() { 40 init(); 41 dp(); 42 printf("%d %d ",ans2, ans1); 43 }
3. luogu P1063 能量项链(最优矩阵链乘
1 /*ps: 不是贪心哦*/ 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 #define MAXN 200+99 6 7 int n,lenth,ans; 8 int f[MAXN][MAXN], head[MAXN], tail[MAXN]; 9 10 int main() { 11 scanf("%d",&n); 12 for(int i = 1; i <= n; i++) scanf("%d",&head[i]), head[i+n] = head[i]; 13 lenth = n*2; //仍然换成链状 14 for(int i = 1; i <= lenth-1; i++) tail[i] = head[i+1];//(注意这还要"-1",因为将1和2n合并的话,只需将1和n合并(下面一样 15 tail[lenth] = head[1]; 16 // for(int i = 1; i <= lenth-1; i++) f[i][i] = 0;//初始化边界 17 for(int L = 1; L <= n-1; L++) //以合并次数为阶段(次数从1开始 18 for(int i = 1; i <= lenth-L; i++) {//以起始位置为状态 19 int j = i+L;//算出结束位置(包括第j个 20 for(int k = i; k < j; k++) {//k 仍然<j 21 f[i][j] = max(f[i][j], f[i][k] + f[k+1][j] + head[i]*tail[k]*tail[j]); //决策 22 } 23 } 24 25 for(int i = 1; i <= n; i++) ans = max(ans, f[i][i+n-1]);//以起点求出最值 26 printf("%d",ans); 27 }