@myecho
2019-04-16T02:34:41.000000Z
字数 5858
阅读 892
动态规划
区间DP一般都可以使用记忆化搜索或者直接DP的方法写。
区间DP在枚举状态时由于每次都是区间减少,所以可以使用区间l作为一维进行枚举。
由矩阵连乘拓展出来的几道习题
1. POJ 1651 Multiplication Puzzle
题意:一排牌/卡片(一串数字),每次从这些牌中拿走一张牌(首尾两张不能拿),把前一张,这一张,后一张牌上的数字相乘的结果累加,直到只剩下两张牌为止。问所能得到的最小结果是多少。
#include<iostream>#include<algorithm>#include<string>using namespace std;int a[101];int m[101][101];#define INF 0x3f3f3f3fint main(){int n;cin >> n;for(int i = 1; i <= n; ++i){cin >> a[i];}for(int i = 1; i <= n; ++i){m[i][i] = 0;m[i][i+1] = 0; //边界还是挺重要的!}for(int l = 3; l <= n; ++l){for(int i = 1; i <= n-l+1; ++i){int j = i+l-1;m[i][j] = INF;for(int k = i+1; k <= j-1; ++k){ //注意k的范围m[i][j] = min(m[i][j], m[i][k]+m[k][j]+a[k]*a[i]*a[j]); //not a[k+1]*a[k]*a[k-1]}}}cout << m[1][n] << endl;return 0;} //http://www.cnblogs.com/hoodlum1980/archive/2012/06/07/2540150.html
所以我们必须在木棍的做左边和左右边分别添加一个点,那么得到的就是整个区间。(套模板)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 50 + 10;
const int INF = ~0U >> 2;
int w[MAXN],dp[MAXN][MAXN];
int L,n;
int solve(){
n++;
for(int i = 0;i <= n;++i)
for(int j = i+1;j <= n;++j)
dp[i][j] = (i+1 == j ? 0 : INF);
w[0] = 0; w[n] = L;
for(int p = 1;p <= n;++p){ //区间长度
for(int s = 0;s <= n-p;++s){ // 起始位置
int e = s + p; //终点
for(int k = s+1;k < e;++k){ //状态转移
dp[s][e] = min(dp[s][e],dp[s][k] + dp[k][e] + w[e] - w[s]);
}
}
}
return dp[0][n];
}
int main()
{
while(scanf("%d",&L),L){
scanf("%d",&n);
for(int i = 1; i <= n; ++i){
scanf("%d",&w[i]);
}
printf("The minimum cutting is %d.\n",solve());
}
return 0;
}
其实dp很难逃出3种思路:
1、一维线性dp:每次考虑i时,选择最优子问题要么在i-1,要么在1...i-1里;
2、二维线性dp:考虑(i,j)子问题时,选择最优子问题要么在(i+1,j)、(i,j-1),要么在i<= k <=j里;
3、树形dp:考虑i节点最优时,选择子节点最优,一般融合了01背包dp的双重dp。
4. LightOJ 1422 Halloween Costumes
此题比较难考虑到区间dp上来,但是题目中通过推理可以判断出类似"断点"的结构。
题目链接;http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=27130
5. poj2955 Brackets(寻求最大的括号匹配数)
题目链接:http://poj.org/problem?id=2955
递推公式:dp[i][j]= max(dp[i+1][j], dp[i+1][k-1]+dp[k][j]+2)其中i+1<=k<=j
我们可以看到从递推公式来看,和上式基本上是同一个问题。还是先找一个最差的情况,在这个题目中,dp[i+1][j]就是最差情况,然后再通过寻找一个k来划分子问题进而看是否可以优化子问题。
6. hdu 2476
题意:给出两个字符串s1,s2,通过刷这个操作,可以将s1中的某个区间内的字符全部改变为另一个字符,求问s1抓换到s2所需的最小的操作数?
需要进行两次dp操作,一次dp[i][j]统计从空串转换到s2所需的最小操作。最后一次ans[j]统计从s1转换到s2的最小的操作数。
题解:http://www.cnblogs.com/ACMDoli/articles/4676197.html
代码:#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;char s1[105],s2[105];int dp[105][105];//dp[i][j]为i~j的刷法int ans[105],i,j,k,len;int main(){while(~scanf("%s%s",s1,s2)){len = strlen(s1);memset(dp,0,sizeof(dp));for(j = 0; j<len; j++){for(i = j; i>=0; i--)//j为尾,i为头{dp[i][j] = dp[i+1][j]+1;//先每个单独刷for(k = i+1; k<=j; k++)//i到j中间所有的刷法{if(s2[i]==s2[k])dp[i][j] = min(dp[i][j],(dp[i+1][k]+dp[k+1][j]));//i与k相同,寻找i刷到k的最优方案}}}for(i = 0; i<len; i++)ans[i] = dp[0][i];//根据ans的定义先初始化for(i = 0; i<len; i++){if(s1[i] == s2[i]){if(i==0)ans[i] = 0;elseans[i] = ans[i-1];//如果对应位置相等,这个位置可以不刷}else{for(j = 0; j<i; j++)ans[i] = min(ans[i],ans[j]+dp[j+1][i]);//寻找j来分割区间得到最优解}}printf("%d\n",ans[len-1]);}return 0;}
#include<iostream>#include<cstdio>#include<cstring>#define N 110#define inf 0x7fffffffusing namespace std;char str[N];struct node{int num; //储存的是压缩后的字符串的长度char s[N];}dp[N][N];int pos;char ts[N];void Int2S(int num){int i,j;pos=-1;char cc;while(num){ts[++pos]=(num%10)+'0';num/=10;}for(j=0;j<=pos/2;j++){cc=ts[j];ts[j]=ts[pos-j];ts[pos-j]=cc;}}int main(){int i,j,k,len,p,q,f,nt,t;scanf("%s",str);len=strlen(str);for(i=0;i<len;i++){dp[i][i].s[0]=str[i];dp[i][i].s[1]='\0';dp[i][i].num=1;}for(p=1;p<len;p++)for(i=0;i<len-p;i++){j=i+p;dp[i][j].num=inf;//开始处理i->j片段内的字符串如何进行打包操作for(k=1;k<p;k++){if((p+1)%k)//以k为长度进行分割continue;q=i+k,t=i;while(q<=j){if(str[q]!=str[t])break;q++;t++;if(t==i+k)t=i;}if(q>j){nt=(p+1)/k;Int2S(nt);strcpy(dp[i][j].s,ts); //最好不直接拷贝,长度无法确定dp[i][j].s[++pos]='(';for(t=0;dp[i][i+k-1].s[t];t++)//将子结构中的最优片段拷贝过来 不能直接拷贝原来的字符串,因为子结构可能完成了压缩dp[i][j].s[++pos]=dp[i][i+k-1].s[t];dp[i][j].s[++pos]=')';dp[i][j].num=pos+1;dp[i][j].s[++pos]='\0';break;}}//递推子结构中的最优结果for(k=i;k<j;k++){if(dp[i][j].num>dp[i][k].num+dp[k+1][j].num){dp[i][j].num=dp[i][k].num+dp[k+1][j].num;strcpy(dp[i][j].s,dp[i][k].s);strcat(dp[i][j].s,dp[k+1][j].s);//会将前边的'\0'覆盖}}}printf("%s\n",dp[0][len-1].s);return 0;}
其共同特点是若A,若B,则AB,都可以将区间的问题通过划分为更小的区间来进行解决,这里需要注意不同问题之间的区别和练习,以及边界条件的处理。
二维DP(较难)
例题 URAL 1900 Brainwashing Device
#include<iostream>#include<vector>#include<algorithm>#include<cstring>using namespace std;const int N=555;const int INF=1e9;int dp[N][N], pt[N][N];int c[N][N], g[N][N];int n, k;/*输入[i][j]即为从i上车,从j下车的乘客数量,从i到j的乘客是需要途径i与j之间的中间车站的关键在于认识到是如何划分成子问题的!*/void pts(int r, int st) //二维DP的路径一般也要使用二维数组进行保存{if(st == 0) return;pts(pt[r][st], st - 1);cout<<r-1;if(st == k) cout<<"";else cout<<" ";}int main(){memset(c,0,sizeof(c));cin>>n>>k;for(int i=1;i<n;++i){for(int j=i+1;j<=n;++j){cin>>c[i][j];c[i][j]+=c[i][j-1]; //相当于输入的每一行前缀之和}}for(int i=1;i<n;++i){for(int j=i+1;j<=n;++j){g[i][j]=0; //g[i][j]代表第i条边到第j条边且在j-1到j之间安装装置产生的happy人数for(int t=i;t<j;t++){g[i][j]+=c[t][n]-c[t][j-1];//依次添加的是 从i到j j+1 ... n车站的乘客人数}}}memset(dp,-1,sizeof(dp));//非负数,数据可能为0dp[1][0]=0;//初始状态for(int i=1;i<=n;++i){for(int j=i+1;j<=n;++j){for(int t=1;t<=min(i,k);++t){ //min(i,k)要注意if(dp[j][t]<dp[i][t-1]+g[i][j]){dp[j][t]=dp[i][t-1]+g[i][j];pt[j][t]=i;}}}}int ans=-1,id;for(int i=k+1;i<=n;i++){if(dp[i][k]>ans){ans=dp[i][k];id=i;}}cout<<ans<<endl;pts(id,k);return 0;}
区间dp做的时候都会在for i 与for j 之后for k 之前添加dp[i][j]= INF(取最小值)或者0(需要取最大值时)之类的东西,防止无法开始比较。得不到初值.
还要注意的一点是题目中的循环顺序,我们可以发现当[i,j]时一定要求[i,k] [k,j]其中子问题的两个问题区间都比原问题的区间要小,因此最外层的循环一般是设置l,l为长度,第二层循环为i(0~n-l),j直接由l和i计算得出。从而保证所有的长度小的子问题已经得到了解决。
矩阵中最大的1
http://www.voidcn.com/article/p-vpbybrua-qz.html
s[i][j] <- s[i-1][j],s[i][j-1],s[i-1][j-1] 得到当前的最优解