zoukankan      html  css  js  c++  java
  • 区间dp总结篇

    前言:这两天没有写什么题目,把前两周做的有些意思的背包题和最长递增、公共子序列写了个总结。反过去写总结,总能让自己有一番收获......就区间dp来说,一开始我完全不明白它是怎么应用的,甚至于看解题报告都看不明白,而到了现在,遇到区间dp之类的题目,我不至于没有任何方向,慢慢的推导,有些题目没有自己想象的那么难,还是可以推导出转移方程的,有些题目,在自己推导过后,与解题报告相对照,也总能有一番全新的收获。我是觉得,解题报告需要看,但是怎么看,如何看,却是值得思量.......

    1、Light oj 1422 Halloween Costumes

    题意:给你n天需要穿的衣服的样式,每次可以套着穿衣服,脱掉的衣服就不能再用了(可以再穿),问至少要带多少条衣服才能参加所有宴会

    http://lightoj.com/volume_showproblem.php?problem=1422

    思路:首先dp[i][j]代表从区间i到区间j需要的最少穿衣服数量,我采取的是从下向上更新的。那么,面临第i件衣服,首先我们考虑穿上它,那么它所在的区间dp[i][j]=dp[i+1][j]+1;

    接着考虑是否可以不用穿上它?在什么条件下,可以不用穿这件衣服呢?只有当区间i+1~~j里面已经穿过这件衣服的时候,就可以考虑不用再穿这件衣服。那么假设i+1<=k<=j

    其中第k件衣服与第i件一样,那么第k件衣服穿上,第i件衣服不穿的情况的最少穿衣数==dp[i+1][k-1]+dp[k][j];第i件衣服不穿,那么就从第i+1件衣服开始计算,第k件衣服存在,那么在这个断点,我们该怎么判断是dp[i+1][k-1]+dp[k][j]呢?可以直接从数据推导出这个关系,也可以这样,在第k区间的时候,a[k]可能不是它自己本身穿的,而是在第k区间到第j区间,存在一个k<tmp<=j,有a[tmp]==a[k]......所以会是dp[i+1][k-1]+dp[k][j]........

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int min(int x,int y)
    {
    	if(x>y)
    	return y;
    	else
    	return x;
    }
    int dp[105][105],a[105];
    int main()
    {
    	int text,h=0;
    	scanf("%d",&text);
    	while(text--)
    	{
    		int n;
    		scanf("%d",&n);
    		for(int i=1;i<=n;i++)
    		scanf("%d",&a[i]);
    		memset(dp,0,sizeof(dp));
    		for(int i=1;i<=n;i++)
    		dp[i][i]=1;
    		for(int i=n-1;i>=1;i--)
    		{
    			for(int j=i+1;j<=n;j++)
    			{
    				dp[i][j]=dp[i+1][j]+1;
    				for(int k=i+1;k<=j;k++)
    				if(a[i]==a[k])
    				{
    					dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
    				}
    			}
    		}
    		printf("Case %d: %d
    ",++h,dp[1][n]);
    	}
    	return 0;
    }
    

    2、 poj2955(括号匹配问题)

    题意:给你一串()[]括号,要你求出这串括号的最大匹配个数,如'('与')'匹配,为2个,'['与']'匹配,为2个,其他不能匹配.......

    思路:dp[i][j]代表从区间i到区间j所匹配的括号的最大个数,首先,假设不匹配,那么dp[i][j]=dp[i+1][j];然后查找i+1~~j有木有与第i个括号匹配的

    有的话,dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k][j]+2).....

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    
    int dp[105][105];
    char a[105];
    int max(int x,int y)
    {
    	if(x>y)
    	return x;
    	else return y;
    }
    int main()
    {
    	while(scanf("%s",a+1)>0&&a[1]!='e')
    	{
    		a[0]=2;
    		int len=strlen(a);
    		len--;
    		//printf("%d
    
    ",len);
    		//for(int i=1;i<=len;i++)
    		//printf("%d %c
    ",i,a[i]);
    		memset(dp,0,sizeof(dp));
    		for(int i=len-1;i>=1;i--)
    		{
    			for(int j=i+1;j<=len;j++)
    			{
    				dp[i][j]=dp[i+1][j];
    				for(int k=i+1;k<=j;k++)
    				{
    					if((a[i]=='('&&a[k]==')')||(a[i]=='['&&a[k]==']'))
    					{
    						dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k][j]+2);
    						//printf("%d %d %c %c
    ",i,k,a[i],a[k]);
    				    }
    				}
    			}
    		}
    		printf("%d
    ",dp[1][len]);
    	}
    	return 0;
    } 
    

     3、poj3280(推荐)

    题意:给你m个字符,其中有n种字符,每种字符都有两个值,分别是增加一个这样的字符的代价,删除一个这样的字符的代价,让你求将原先给出的那串字符变成回文串的最小代价。

     思路:dp[i][j]代表区间i到区间j成为回文串的最小代价,那么对于dp[i][j]有三种情况:

    1、dp[i+1][j]表示区间i到区间j已经是回文串了的最小代价,那么对于s[i]这个字母,我们有两种操作,删除与添加,对应有两种代价,dp[i+1][j]+add[s[i]],dp[i+1][j]+del[s[i]],取这两种代价的最小值;

    2、dp[i][j-1]表示区间i到区间j-1已经是回文串了的最小代价,那么对于s[j]这个字母,同样有两种操作,dp[i][j-1]+add[s[j]],dp[i][j-1]+del[s[j]],取最小值

    3、若是s[i]==s[j],dp[i+1][j-1]表示区间i+1到区间j-1已经是回文串的最小代价,那么对于这种情况,我们考虑dp[i][j]与dp[i+1][j-1]的大小........

    然后dp[i][j]取上面这些情况的最小值.........

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[2005][2005],add[27],del[27];
    char s[2005];
    int min(int x,int y)
    {
    	if(x>y)
    	return y;
    	else
    	return x;
    }
    int main()
    {
    	int n,m;
    	while(scanf("%d%d",&n,&m)>0)
    	{
    		scanf("%s",s+1);
    		s[0]=2;
    		for(int i=1;i<=n;i++)
    		{
    			char ch[10];
    			int tmp1,tmp2;
    			scanf("%s%d%d",ch,&tmp1,&tmp2);
    			add[ch[0]-'a'+1]=tmp1;
    			del[ch[0]-'a'+1]=tmp2;
    		}
    		memset(dp,0,sizeof(dp));
    		for(int i=m-1;i>=1;i--)
    		{
    			for(int j=i+1;j<=m;j++)
    			{
    				dp[i][j]=min(dp[i+1][j]+add[s[i]-'a'+1],dp[i+1][j]+del[s[i]-'a'+1]);
    				int tmp=min(dp[i][j-1]+add[s[j]-'a'+1],dp[i][j-1]+del[s[j]-'a'+1]);
    				dp[i][j]=min(dp[i][j],tmp);
    				if(s[i]==s[j])
    				dp[i][j]=min(dp[i][j],dp[i+1][j-1]);
    			}
    		}
    		printf("%d
    ",dp[1][m]);
    	}
    	return 0;
    }
    

     4、poj1141(区间dp记录路径问题)

    题意:给出一串括号,要你补上最少的括号使这一串括号都匹配........

    思路:dp[i][j]表示从区间i到区间j使其所以括号匹配需要补上的最少括号数,那么当出现一个括号时,首先考虑它不与后面匹配的情况,那么需要加一个相对应的括号,让之匹配dp[i][j]=dp[i+1][j]+1;

    然后再考虑,若是后面有括号可以让它匹配的情况,那么假设i<k<=j,当s[i]=='('&&s[k]==')'的时候,考虑动态转移,dp[i][j]=dp[i+1][k-1]+dp[k][j]-1

    为什么这个动态方程减1呢,因为我将与之匹配的那个括号重新计算了一次,当s[k]==')'的时候,在计算dp[k][k]的时候,我的状态转移已经把这个括号自动匹配了一次,所以要减去这次匹配的........

    然后就是记录路径了,开一个二维数组a[i][j],当a[i][j]==-1的时候,表示dp[i][j]这个状态是从dp[i+1][j]推导过来的,当a[i][j]>0的时候,表示dp[i][j]是从dp[i+1][a[i][j]-1]以及dp[k][j]这两个状态推导过来的,那么注意到当a[i][j]!=-1的时候,就正好表示s[i]与s[a[i][j]]匹配,说明在第i个括号这个地方只需要输出它自己本身,其他的,若是a[i][j]==-1的,都需要输出它自身以及与它自身匹配的括号.........

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[300][300],a[300][300],b[300];
    char s[300];
    void print(int i,int j)
    {
    	if(i>=j)
    	return;
    	if(a[i][j]==-1)
    	print(i+1,j);
    	if(a[i][j]>0)
    	{
    		b[i]=1;
    		b[a[i][j]]=1;
    		print(i+1,a[i][j]-1);
    		print(a[i][j],j);
    	}
    }
    int main()
    {
    	while(gets(s+1)>0)    //这里注意,有直接输入
    的情况.........
    	{
    		s[0]=2;
    		memset(dp,0,sizeof(dp));
    		memset(a,-1,sizeof(a));
    		memset(b,0,sizeof(b));
    		int len=strlen(s);
    		len--;
    		for(int i=1;i<=len;i++)
    		dp[i][i]=1;
    		for(int i=len-1;i>=1;i--)
    		{
    			for(int j=i+1;j<=len;j++)
    			{
    				dp[i][j]=dp[i+1][j]+1;
    				a[i][j]=-1;
    				for(int k=i+1;k<=j;k++)
    				if((s[i]=='('&&s[k]==')')||(s[i]=='['&&s[k]==']'))
    				{
    					if(dp[i][j]>dp[i+1][k-1]+dp[k][j]-1)
    					{
    						dp[i][j]=dp[i+1][k-1]+dp[k][j]-1;
    						a[i][j]=k;
    					}
    				}
    			}
    		}
    		print(1,len);
    		for(int i=1;i<=len;i++)
    		{
    			if(b[i]==1)
    			printf("%c",s[i]);
    			else
    			{
    				if(s[i]=='('||s[i]==')')
    				printf("()");
    				else
    				printf("[]");
    			}
    		}
    		printf("
    ");
    	}
    	return 0;
    }
    

     5、poj1651(推荐)

    题意:给你一组数字,第一个和最后一个数字不可以取出去,其它任意取出去,当你要取出一个数字时,它有一个代价,这个代价就是与它相邻的两个数的乘积,求除了首位两位数字,把其他数字都取出来,它们的代价之和的最小值........

    思路:这题目,只有自己做过才能体会......我是说不出来......

    Sample Input
    6
    10 1 50 50 20 5
    Sample Output
    3650
    
    #include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[105][105],a[105]; int min(int x,int y) { if(x>y) return y; else return x; } int main() { int n; while(scanf("%d",&n)>0) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); for(int i=n-2;i>=1;i--) { dp[i][i+2]=a[i]*a[i+1]*a[i+2]; for(int j=i+3;j<=n;j++) { dp[i][j]=a[i]*a[i+1]*a[j]+dp[i+1][j]; dp[i][j]=min(dp[i][j],a[i]*a[j-1]*a[j]+dp[i][j-1]); for(int k=i+2;k<j-1;k++) dp[i][j]=min(a[i]*a[k]*a[j]+dp[i][k]+dp[k][j],dp[i][j]); } } printf("%d ",dp[1][n]); } return 0; }

     6、poj3661(推荐)

    题意:给你一个n,m,n表示有n分钟,每i分钟对应的是第i分钟能跑的距离,m代表最大疲劳度,每跑一分钟疲劳度+1,当疲劳度==m,必须休息,在任意时刻都可以选择休息,如果选择休息,那么必须休息到疲劳度为0,当然,当疲劳度为0的时候也是可以继续选择休息的,求在n分钟后疲劳度为0所跑的最大距离

    思路:dp[i][j]表示在第i分钟疲劳度为j的时候所跑的最大距离,dp[i][j]=dp[i-1][j-1]+d[i];这个转移,说的是,第i分钟选择跑步,当然,第i分钟可以选择不跑步,那么就是选择休息,题目说了,选择休息的话必须要休息到疲劳度为0才可以跑,那还有一点,当疲劳度为0了,还是选择继续休息呢?dp[i][0]=dp[i-1][0];
    选择休息,那么疲劳度到0了,这一点的最大距离怎么做呢?dp[i][0]=max(dp[i][0],dp[i-k][k])   (0<k<=m&&i-k>0)

    这样所有的状态都考虑完了,可以写代码了.......

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[10005][505],a[10005];
    int main()
    {
    	int n,m;
    	while(scanf("%d%d",&n,&m)>0)
    	{
    		for(int i=1;i<=n;i++)
    		scanf("%d",&a[i]);
    		memset(dp,0,sizeof(dp));
    		for(int i=1;i<=n;i++)
    		{
    			for(int j=1;j<=m;j++)
    			dp[i][j]=dp[i-1][j-1]+a[i];
    			dp[i][0]=dp[i-1][0];
    			for(int k=1;k<=m;k++)
    			if(i-k>=0)
    			dp[i][0]=max(dp[i][0],dp[i-k][k]);
    		}
    		printf("%d
    ",dp[n][0]);
    	}
    	return 0;
    }
    

     7、hdu2476(推荐)

    给出两串字符,要你将第一串字符变成第二串字符,每次可以改变连续的一个或多个字符,求最少的修改次数

    思路:还是区间dp问题,说起来也挺简单的,只是记录路径问题,有些难以想到..........

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[105][105],a[105];
    char s[105],t[105];
    int main()
    {
        while(scanf("%s",s+1)>0)
        {
            scanf("%s",t+1);
            s[0]=t[0]=2;
            int len=strlen(s);
            len--;
            memset(dp,0,sizeof(dp));
            memset(a,0,sizeof(a));
            for(int i=1;i<=len;i++)
            dp[i][i]=1;
            for(int i=len-1;i>=1;i--)
            {
                for(int j=i+1;j<=len;j++)
                {
                    //if(s[i]!=t[i])
                    dp[i][j]=dp[i+1][j]+1;
                    for(int k=i+1;k<=j;k++)
                    if(t[i]==t[k])
                    {
                        dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
                    }
                }
            }
            for(int i=1;i<=len;i++)
            {
                a[i]=dp[1][i];
                if(s[i]==t[i])
                {
                    if(i==1)
                    a[i]=0;
                    else
                    a[i]=a[i-1];
                }
                else
                for(int j=1;j<i;j++)
                a[i]=min(a[i],a[j]+dp[j+1][i]);
                
            }
            printf("%d
    ",a[len]);
    
            //printf("%d
    ",dp[1][len]);
        }
        return 0;
    }
    

     8、zoj3537(最优三角划分)

    9、编辑距离问题

    题目描述:

    要求两字符串有差异的字符个数。例如: 
    aaaaabaaaaa 
    aaaaacaabaa 
    这两个字符串,最大公共字串长度是5,但它们只有两个字符不同,函数输出值应为2。 
    如果是: 
    aaabbbcccddd 
    aaaeeeddd 
    函数的输出值应该是6。 

    比较形象地形容一下,把两个字符串排成上下两行,每个字符串都可以在任何位置插入空格以便上下对齐,每个列上至少有一个字符来自这两个字符串。当对齐程度最高的时候,没有对上的列的数即为函数输出值。 
    aaabbbcccddd 
    aaaeeeddd 
    最优对齐状态是: 
    aaabbbcccddd 
    aaaeee     ddd 
    没有对上的列是6,函数输出值为6。 
    如果是: 
    abcde 
    acefg 
    最优对齐状态是: 
    abcde 
    a  c  efg 
    没有对上的列数是4,函数输出值为4。

     

     

    问题抽象归类:(编辑距离问题)

    设A和B是2个字符串。要用最少的字符操作将字符串A转换为字符串B。这里所说的字符操作包括:

    (1)删除一个字符;
    (2)插入一个字符;
    (3)将一个字符改为另一个字符。
    将字符串A变换为字符串B所用的最少字符操作数称为字符串A到B的编辑距离,记为d(A,B)。试设计一个有效算法,对任给的2个字符串A和B,计算出它们的编辑距离d(A,B)。
    要求:
    输入:第1行是字符串A,第2行是字符串B。
    输出:字符串A和B的编辑距离d(A,B)

     

     

    思路:动态规划

    开一个二维数组d[i][j]来记录a0-ai与b0-bj之间的编辑距离,要递推时,需要考虑对其中一个字符串的删除操作、插入操作和替换操作分别花费的开销,从中找出一个最小的开销即为所求

    具体算法:

    首先给定第一行和第一列,然后,每个值d[i,j]这样计算:d[i][j]   =   min(d[i-1][j]+1,d[i][j-1]+1,d[i-1][j-1]+(s1[i]  ==  s2[j]?0:1));   
     最后一行,最后一列的那个值就是最小编辑距离 

     

    #include <stdio.h>   
    #include <string.h>   
    char s1[1000],s2[1000];   
    int min(int a,int b,int c) {   
    	int t = a < b ? a : b;   
    	return t < c ? t : c;   
    }   
    void editDistance(int len1,int len2)
    {   
    	int** d=new int*[len1+1];
    	for(int k=0;k<=len1;k++)
    		d[k]=new int[len2+1];  
    	int i,j;   
    	for(i = 0;i <= len1;i++)   
    		d[i][0] = i;   
    	for(j = 0;j <= len2;j++)   
    		d[0][j] = j;   
    	for(i = 1;i <= len1;i++)   
    		for(j = 1;j <= len2;j++)
    		{   
    			int cost = s1[i] == s2[j] ? 0 : 1;   
    			int deletion = d[i-1][j] + 1;   
    			int insertion = d[i][j-1] + 1;   
    			int substitution = d[i-1][j-1] + cost;   
    			d[i][j] = min(deletion,insertion,substitution);   
    		}   
    		printf("%d
    ",d[len1][len2]); 
    		for(int k=0;i<=len1;k++)
    			delete[] d[k];
    		delete[] d;
    }   
    int main()
    {   
    	while(scanf("%s %s",s1,s2) != EOF)   
    		editDistance(strlen(s1),strlen(s2));   
    }  
    
  • 相关阅读:
    StarUML 破解方法
    String、StringBuilder、StringBuffer对比
    ThreadLocal源码
    编程思想——访问权限控制
    设计模式——调停者模式
    Abp.vNext 权限备注
    Abp 中 模块 加载及类型自动注入 源码学习笔记
    使用 ZipArchive 生成Zip文件备注
    ORACLE 连接SQLSERVER 数据库备忘
    FastReport 自定义数据集
  • 原文地址:https://www.cnblogs.com/ziyi--caolu/p/3236035.html
Copyright © 2011-2022 走看看