石子合并
两堆石子的合并:
例如:dp[1][2] = dp[1][1] + dp[2][2] + sum[1][2];
总结:dp[i][i+1] = dp[i][i] + dp[i+1][i+1] + sum[i][i+1];
三堆石子的合并:
dp[1][3] = min(dp[1][1]+dp[2][3],dp[1][2]+dp[3][3])+sum[1][3];
总结:dp[i][i+2]=min(dp[i][i]+dp[i+1][i+2],dp[i][i+1]+dp[i+2][i+2])+sum[i][i+2];
minval()平行四边形优化
- 在它的三重循环中,前两重循环枚举所有可能的合并,无法优化。最后一层循环枚举分割点k,可以优化。因为每次运行最后一层循环时都在某个子区间内部寻找最优分割点,该操作在多个子区间里是重复的;如果找到后这个最优点后保存下来,用于下一次循环,就能避免重复计算,从而降低复杂度。
- 用s[i][j]表示区间[i, j]中的最优分割点,第三重循环从区间[i, j-1)的枚举,优化到在区间[s[i][j-1], s[i+1][j]]中枚举。
- 上述原理就是“平行四边形优化”。
复杂度接近O(n2),可以解决n < 3000的问题
#include <cstdio>
const int INF = 1 << 30 ;
const int N = 100 ;
int sum[N] ; //sum[i]为前i项和
int n , l , r ;
int minval(){
int dp[N][N] ; //dp[i][j]记录从i到j花费的最少时间
int s[N][N] ; //记录最优分割点
for (int i = 1 ; i <= n ; i ++){
dp[i][i] = 0 ;
s[i][i] = i ;
}
for (int len = 1 ; len < n ; len ++){
for (int i = 1 ; i <= n-len ; i ++){
int j = i + len ;
dp[i][j] = INF ;
for (int k = s[i][j-1] ; k <= s[i+1][j] ; k ++){
if (dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1] < dp[i][j]){
dp[i][j] = dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1] ;
s[i][j] = k ;
}
}
}
}
return dp[1][n] ;
}
int main(){
int x ;
while(~scanf("%d%d%d",&n,&l,&r)){
sum[0] = 0 ;
for (int i = 1 ; i <= n ; i ++){
scanf("%d",&x) ;
sum[i] = sum[i-1] + x ;
}
printf ("%d\n",minval());
}
return 0 ;
}
poj3280(达成回文字符串的最小消耗/最长回文字符串)
poj 3280 Cheapest Palindrome
题意:给出一串字符串和增加删除各个字符的费用,求是字符串变成回文字符串的最小花费。
题解:用w[i] 表示对相应字符串进行操作的最小费用,用dp[i][j] 表示在区间i ~ j 内是字符串变成回文字符串的最小花费。
①、若s[i] == s[j] , dp[i][j] = dp[i+1][j-1] .
程序有两层循环,外层i枚举起点,内层j枚举终点,状态转移方程从小区间到大区间,因此i从尾部开始,回退到起点扩展成大区间
②、若dp[i+1][j]是回文字符串 , dp[i][j] = dp[i+1][j] + w[j] ;
若 dp[i][j-1]是回文字符串 , dp[i][j] = dp[i][j-1] + w[i] ;
#include <cstdio>
#include <map>
#include <algorithm>
using namespace std ;
const int N = 2005 ;
map<char,int> p ;
char str[N] ;
int dp[N][N] ;
int main(){
int n , m ;
scanf ("%d%d",&n,&m) ;
scanf ("%s",str) ;
char ch ;
int x , y ;
for (int i = 0 ; i < n ; i ++){
getchar() ;
scanf ("%c %d %d",&ch,&x,&y) ;
p[ch] = min(x,y) ;
}
for (int i = m-1 ; i >= 0 ; i --){
for (int j = i+1 ; j < m ; j ++){
if (str[i] == str[j])
dp[i][j] = dp[i+1][j-1] ;
else
dp[i][j] = min(dp[i+1][j]+p[str[i]] , dp[i][j-1]+p[str[j]]) ;
}
}
printf ("%d\n",dp[0][m-1]) ;
return 0 ;
}
回文字符串子序列个数
hdu 4632
题意:给出一串字符串,要求找出最多的回文字符串。
题解:用dp[i][j]表示i~j内最多的回文字符串,
①、当s[i] != s[j] 时,根据容斥原理可得: dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1] .
②、当s[i] == s[j] 时,dp[i][j] = dp[i+1][j-1] + 1 ;
#include <cstdio>
#include <cstring>
using namespace std ;
const int N = 1005 ;
const int mod = 10007 ;
int dp[N][N] ;
int main(){
int t ;
scanf ("%d",&t) ;
for (int cnt = 1 ; cnt <= t ; ++ cnt){
char s[N] ;
scanf ("%s",s) ;
int n = strlen(s) ;
memset(dp,0,sizeof(dp)) ;
for (int i = 0 ; i <= n ; ++ i)
dp[i][i] = 1 ; //单个字符也算一个回文串
for (int j = 0 ; j < n ; ++ j){
for (int i = j-1 ; i >= 0 ; i --){
dp[i][j] = (dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1] + mod)%mod ; //+mod是为了防止为负值
if (s[i] == s[j])
dp[i][j] = dp[i][j] + dp[i+1][j-1] + 1 ;
dp[i][j] %= mod ;
}
}
printf ("Case %d: %d\n",cnt,dp[0][n-1]) ;
}
return 0 ;
}
hdu5115
hdu5115 Dire Wolf
题意:每一只狼都有初始的攻击值还有给旁边狼增加的攻击值,求最小猎人消灭狼的攻击值。
#include <cstdio>
#include <algorithm>
using namespace std ;
const int N = 205 ;
const int INF = 0x3f3f3f3f ;
int a[N] , b[N] , dp[N][N] ;
int main(){
int t ;
scanf ("%d",&t) ;
for (int cnt = 1 ; cnt <= t ; ++ cnt){
int n ;
scanf ("%d",&n) ;
b[0] = b[n+1] = 0 ;
for (int i = 1 ; i <= n ; ++ i) scanf("%d",&a[i]) ;
for (int i = 1 ; i <= n ; ++ i) scanf("%d",&b[i]) ;
//初始化
for (int i = 1 ; i <= n ; ++ i)
for (int j = 1 ; j <= n ; ++ j)
dp[i][j] = INF ;
for (int i = 1 ; i <= n ; ++ i){
dp[i][i] = a[i] + b[i-1] + b[i+1] ;
}
for (int len = 1 ; len < n ; ++ len){
for (int i = 1 ; i <= n-len ; ++ i){
int j = i + len ;
for (int k = i ; k <= j ; ++ k){
if (k == i)
dp[i][j] = min(dp[i][j],dp[i+1][j]+a[i]+b[i-1]+b[j+1]) ;
else if (k == j)
dp[i][j] = min(dp[i][j],dp[i][j-1]+a[j]+b[i-1]+b[j+1]) ;
else
dp[i][j] = min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]) ;
}
}
}
printf ("Case #%d: %d\n",cnt,dp[1][n]) ;
}
return 0 ;
}
poj 1651
poj 1651 Multiplication Puzzle
题意:有n张卡片,每一张都有值,要求最左和最右两张不能抽其他都可以,每抽取一张,总价值增加该卡片的值和该卡片左右卡片的值的乘积,要求找出最小的总价值。
题解:要注意分割的时候的意思是抽一个卡片出来,所以这个卡片不能在已经抽出的状态里面,所以dp[i][j]里面是不包含j卡片的!即dp[i][j]表示从第i张卡片到第j-1张卡片的最小总价值。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std ;
const int N = 105 ;
const int INF = 0x3f3f3f ;
int a[N] , dp[N][N] ;
int main(){
int n ;
scanf ("%d",&n) ;
memset(dp,INF,sizeof(dp)) ;
for (int i = 1 ; i <= n ; ++ i)
scanf ("%d",&a[i]) , dp[i][i] = 0 ;
for (int len = 1 ; len <= n ; ++ len){
for (int i = 2 ; i <= n-len ; ++ i){ //最左边不能抽所以i的下标从2开始
int j = i + len ;
for (int k = i ; k <= j ; ++ k)
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j] + a[i-1]*a[j]*a[k]) ;
}
}
printf ("%d\n",dp[2][n]) ; //要求左右两张都不能抽
return 0 ;
}