区间dp

石子合并

两堆石子的合并:
例如: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 ; 
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值