区间dp
一、理解区间dp
区间dp就是根据划分区间,找到起止点,然后在很多个小区间中找到最优解。但是在做题过程中会很难找到这个划分点在哪里,然后又如何比较出最优值 ,这个是我不太会的点。然后我在看csdn中找到一个博主的通用公式感觉很好
(这里是他的原博客)然后就可以根据他的一个这样的公式,在面对不同区间dp题目时都差不多是这样的框架。
通用公式:
for(int i=1;i<=n;i++)
{
dp[i][i]=初始值
}
for(int len=2;len<=n;len++)//区间的长度
for(int i=1;i<=n;i++) //枚举起点
{
int j=i+len-1; //区间终点
if(j>n) break; //越界结束
for(int k=i;k<j;k++) //枚举分割点,构造状态转移方程
{
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
}
}
二、经典的例题
A.
Gappu has a very busy weekend ahead of him. Because, next weekend is Halloween, and he is planning to attend as many parties as he can.
Since it’s Halloween, these parties are all costume parties, Gappu
always selects his costumes in such a way that it blends with his
friends, that is, when he is attending the party, arranged by his
comic-book-fan friends, he will go with the costume of Superman, but
when the party is arranged contest-buddies, he would go with the
costume of ‘Chinese Postman’.
Since he is going to attend a number of parties on the Halloween night, and wear costumes accordingly, he will be changing his costumes
a number of times. So, to make things a little easier, he may put on
costumes one over another (that is he may wear the uniform for the
postman, over the superman costume). Before each party he can take off
some of the costumes, or wear a new one. That is, if he is wearing the
Postman uniform over the Superman costume, and wants to go to a party
in Superman costume, he can take off the Postman uniform, or he can
wear a new Superman uniform. But, keep in mind that, Gappu doesn’t
like to wear dresses without cleaning them first, so, after taking off
the Postman uniform, he cannot use that again in the Halloween night,
if he needs the Postman costume again, he will have to use a new one.
He can take off any number of costumes, and if he takes off k of the
costumes, that will be the last k ones (e.g. if he wears costume A
before costume B, to take off A, first he has to remove B).
Given the parties and the costumes, find the minimum number of costumes Gappu will need in the Halloween night.
Input
Input starts with an integer T (≤ 200), denoting the number of test cases.
Each case starts with a line containing an integer N (1 ≤ N ≤ 100) denoting the number of parties. Next line contains N integers,
where the ith integer ci (1 ≤ ci ≤ 100) denotes the costume he will be
wearing in party i. He will attend party 1 first, then party 2, and so
on.
Output
For each case, print the case number and the minimum number of required costumes.
题目的大致意思就是这个人要去参加派对穿礼服,然后他在每个地方穿的礼服啊可以一次的穿在身上,也可以到那件穿那件,然后问他最少带衣服的数。
int main()
{
int T;
scanf("%d",&T);
int l =0;
while(T--)
{
int n;
scanf("%d",&n);
int a[maxn];
memset(a,0,sizeof(a));
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
for(int i=0;i<n;i++)
dp[i][i]= 1;
for(int len = 2;len<=n; len++)
for(int i=0;i<n;i++)
{
int j = len + i -1;
if(j>=n) break;
dp[i][j] = dp[i][j-1] + 1; //第j天穿不穿,如果穿就加1
for(int k=i;k<=j-1;k++)
if(a[k]==a[j])//如果不穿,则考虑的j天和k天如果穿的一样中间要脱掉件件衣服最少
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j-1]);
}
printf("Case %d: %d\n",++l,dp[0][n-1]);
}
return 0;
}
B.
Sample Input
()()()
([]])
)[)(
([][][)
Sample Output
6
4
0
4
题目的要求就是求最多成对的括号(),【】;
int judge(int x,int y){
if(str[x]=='('&&str[y]==')')return 1;
if(str[x]=='['&&str[y]==']')return 1;
return 0;
}
int main(){
while(gets(str)){
if(strcmp(str,"end")==0)break;
memset(dp,0,sizeof(dp));
int len=strlen(str);
for(int t=1;t<len;t++){
for(int i=0;i+t<len;i++){
int j=i+t;
if(judge(i,j)){
if(j-i==1)dp[i][j]=2;
else dp[i][j]=dp[i+1][j-1]+2;
}
for(int k=i;k<j;k++){
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
}
}
}
cout<<dp[0][len-1]<<endl;
}
}
C.
For example, if cards in the row contain numbers 10 1 50 20 5, player might take a card with 1, then 20 and 50, scoring
10*1*50 + 50*20*5 + 10*50*5 = 500+5000+2500 = 8000
If he would take the cards in the opposite order, i.e. 50, then 20, then 1, the score would be
1*50*20 + 1*20*5 + 10*1*5 = 1000+100+50 = 1150.
题目要求就是找到这样相乘的最大的
int solve(int i,int j)
{
if(dp[i][j]!=INF)return dp[i][j];
if(j==i+1)return dp[i][j]=0;
for(int k=i+1;k<j;k++)
dp[i][j]=min(dp[i][j],a[k]*a[i]*a[j]+solve(i,k)+solve(k,j));
return dp[i][j];
}
D.
不会,哈哈哈哈。于是我找到大佬们的解题,虽然也没有太看懂,但多看看应该就好了,熟能生巧嘛,代码来源这里。然后大佬说的是凸包和区间dp和最优三角刨分。然后我就又找到两个大佬分呗对凸包和凸多边行的最优解动态规划。哈哈哈,都好复杂感觉反正多积累看看应该会明白的,我刚上来对区间dp也是很迷茫,一直到我看见那个博主对区间dp对这个的讲解,希望我也可以从一个看其他人的小菜鸟到别人看我吧,哈哈哈。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1005;
const int inf = 1000000000;
struct point {
int x, y;
} p[maxn], save[maxn], tmp[maxn];
int cost[maxn][maxn], n, m;
int dp[maxn][maxn];
int dis(point p1, point p2, point p0) {
return (p1.x-p0.x) * (p2.y-p0.y) - (p2.x-p0.x) * (p1.y-p0.y);
}
bool cmp(const point &a, const point &b) {
if (a.y == b.y) return a.x < b.x;
return a.y < b.y;
}
int Graham(point *p,int n) {
sort(p,p + n,cmp);
save[0] = p[0];
save[1] = p[1];
int top = 1;
for (int i = 0;i < n; i++) {
while (top && dis(save[top],p[i],save[top-1]) >= 0) top--;
save[++top] = p[i];
}
int mid = top;
for(int i = n - 2; i >= 0; i--) {
while (top > mid && dis(save[top],p[i],save[top-1])>=0) top--;
save[++top]=p[i];
}
return top;
}
int Count(point a, point b) {
return (abs(a.x+b.x) * abs(a.y+b.y)) % m;
}
int main() {
while (scanf("%d%d",&n,&m) != EOF) {
for (int i = 0; i < n; ++i)
scanf("%d%d",&p[i].x,&p[i].y);
int tot = Graham(p,n); //求凸包
if (tot != n) printf("I can't cut.\n");
else {
memset(cost,0,sizeof(cost));
for (int i = 0; i < n; ++i)
for (int j = i + 2; j < n; ++j)
cost[i][j] = cost[j][i] = Count(save[i],save[j]);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j)
dp[i][j] = inf;
dp[i][(i+1)%n] = 0;
}
for (int i = n - 3; i >= 0; i--)
for (int j = i + 2; j < n; j++)
for (int k = i + 1; k <= j - 1; k++)
dp[i][j] = min(dp[i][j], dp[i][k]+dp[k][j]+cost[i][k]+cost[k][j]);
printf("%d\n",dp[0][n-1]);
}
}
return 0;
}
E.
Input
The input contains multiple test cases, separated with a blank line. Each case is started with three integers N ( 1 <= N <= 1000 ), V ( V > 0), X ( X >= 0 ), then N lines followed. Each line contains two integers Xi ( Xi >= 0 ), Bi ( Bi >= 0), which are described above.
You can safely assume that all numbers in the input and output will be less than 231 - 1.
Please process to the end-of-file.
Output
For each test case please output a single number, which is the minimal sum of Displeasure Index. One test case per line.
Sample Input
5 1 0
1 1
2 2
3 3
4 4
5 5
Sample Output
55
题目的大致意思就是一个快递员送餐,然后每位顾客每分钟都有自己的不满意程度,然后快递员要将他们的不满意程度最低是多少。这一题我最初的思路是错误的,所有一直都没有做他,然后我就上网搜了这道题目的答案,哈哈哈。然后发现原来可以这样做。dp[i][j][[0]表示送完[i,j]区间停在左边,dp[i][j][1]表示送完[i,j]区间停在右边。
typedef long long ll;
ll dp[1010][1010][2];
struct node
{
ll x,y;
}pp[1010];
bool cmp(node a,node b)
{ return a.x<b.x;}
ll sum[1010];
int main()
{
ll n,r,P;
while(~scanf("%lld%lld%lld",&n,&r,&P))
{ for(ll i=1;i<=n;i++)
scanf("%lld%lld",&pp[i].x,&pp[i].y);
n++;
pp[n].x=P;
pp[n].y=0;
sort(pp+1,pp+n+1,cmp);
sum[0]=0;
for(ll i=1;i<=n;i++)
sum[i]=sum[i-1]+pp[i].y;
ll tmp;
for(ll i=1;i<=n;i++)
if(pp[i].x==P)
{ tmp=i;break; }
for(ll i=1;i<=n;i++)
for(ll j=1;j<=n;j++)
dp[i][j][0]=dp[i][j][1]=INF;
dp[tmp][tmp][0]=dp[tmp][tmp][1]=0;
for(ll i=tmp;i>=1;i--)
for(ll j=tmp;j<=n;j++)
{
if(i==j)continue;
dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(sum[i]+sum[n]-sum[j])*(pp[i+1].x-pp[i].x));
dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(sum[i]+sum[n]-sum[j])*(pp[j].x-pp[i].x));
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(sum[i-1]+sum[n]-sum[j-1])*(pp[j].x-pp[i].x));
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(sum[i-1]+sum[n]-sum[j-1])*(pp[j].x-pp[j-1].x)); } printf("%lld\n",r*min(dp[1][n][0],dp[1][n][1])); }
return 0;}
F.
InputInput contains multiple cases. Each case consists of two lines:
The first line contains string A.
The second line contains string B.
The length of both strings will not be greater than 100.
Output
A single line contains one integer representing the answer
Sample Input
zzzzzfzzzzz
abcdefedcba
abababababab
cdcdcdcdcdcd
Sample Output
6
7
题目大致意思就是将上下两行字母变成一样的进行。
int main()
{
while(scanf("%s%s",s1,s2)!=EOF)
{
int len=strlen(s1);
memset(dp,0,sizeof(dp));
for(int j=0; j<len; j++)
{
for(int i=j; i>=0; i--)
{
dp[i][j]=dp[i+1][j]+1;
for(int k=i+1; k<=j; k++)
{
if(s2[i]==s2[k])
dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]);
}
}
}
for(int i=0; i<len; i++)
ans[i]=dp[0][i];
for(int i=0; i<len; i++)
{
if(s1[i]==s2[i])
{
ans[i]=ans[i-1];
}
else
{
for(int j=0; j<i; j++)
{
ans[i]=min(ans[i],ans[j]+dp[j+1][i]);
}
}
}
printf("%d\n",ans[len-1]);
}
return 0;
}
这就是我最近做到区间dp的题目有的是网上搜的,有的是自己做到,我感觉这一块真的有一点的难,就是你大概知道是啥意思,但是在写dp的时候,如何找到那个区间已经那个分割点的时候很迷茫,哈哈哈哈。但是看完那个公式之后感觉就好很多了,然后最近学的背包问题就相对简单一点了,例题也大多都会而且都可以很快明白是啥意思要干啥,所有还是最dp的一些东西经常积累一些。