区间DP【5.8】

区间DP

T1:玩雪的小Y

就是能量项链嘛(大雾弥漫
解题思路:这是区间dp的入门题。首先是项链是一个环,我们把他拆成一条链来看,依次枚举长度,再枚举左端点,确定右端点,再从左端点到右端点枚举中间点来更新最优值。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int n,a[205],dp[205][205],maxn;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]),a[i+n]=a[i];
    for(int l=3;l<=n+1;l++){//枚举长度,因为把环拆成链,所以长度最长为n+1(自行思考)
    	for(int i=1;i+l-1<=2*n;i++){//在长度的限制先,保证左端点合法
    		int j=i+l-1;//确定右端点
    		dp[i][j]=-0x3f3f3f3f;//给区间初始化
    		for(int k=i+1;k<j;k++){//选取中间点
    			dp[i][j]=max(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]);//根据中间点不断更新答案
    		}
		}
    }
    for(int i=1;i<=n;i++)maxn=max(maxn,dp[i][i+n]);
	printf("%d",maxn);
    return 0;
}

T2:颜色联通块

来源:CF1114D

思路:

这道题呢重点在p是已经定了的值,所以我们只需要在输入的同时就把重复的颜色相同但是长短不一的连通块全都处理成一块。

for(int i=1;i<=n;i++){
	x=read();
	if(x==a[i-1]){
		i--;
		n--;
	}
	else a[i]=x;
}

这道题呢,与删除字符串略有不同,这道题不需要枚举中点,第一层循环枚举区间长度, 第二层循环枚举可行的左区间, j是算出的右区间, 注意不同题目不同分析,有的题目必须给f赋一个很大的初值再更新之类的,而此题可以不用这样。
那么现在就是判断啦,这道题需要判断两种情况:

两边的颜色相同,那就是中间的长度再加上1(因为是合并)
两边颜色不同,那就在两边分别取最小即可 。
由此,就可以得到状态转移方程了

		if (a[i]==a[j])dp[i][j]=dp[i+1][j-1]+1;
   		else dp[i][j]=min(dp[i+1][j],dp[i][j-1])+1;

最后的目标就是 d p [ 1 ] [ n ] dp[1][n] dp[1][n] 啦。QAQ~

AC代码:

#include<cstdio>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#define min(a,b) a<b?a:b
static char buf[100000],*pa=buf,*pd=buf;
#define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
inline int read(){
    register int x(0);register char c(gc);
    while(c<'0'||c>'9')c=gc;
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
    return x;
}
int n,x,a[5005],dp[5005][5005][2];
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		x=read();
		if(x==a[i-1]){
			i--;
			n--;
		}
		else a[i]=x;
	}
	for(int l=2;l<=n;l++)
		for(int i=1;i+l-1<=n;i++){
			int j=i+l-1;
			dp[i][j][0]=min(dp[i+1][j][0]+(a[i]!=a[i+1]),dp[i+1][j][1]+(a[i]!=a[j]));
			dp[i][j][1]=min(dp[i][j-1][1]+(a[j]!=a[j-1]),dp[i][j-1][0]+(a[i]!=a[j]));
		}
	printf("%d",min(dp[1][n][0],dp[1][n][1]));
	return 0;
}

T3:凸多边形的划分

思路:

我们考虑了由顶点 1 1 1 n n n构成的三角形后,剩下的两部分都是与原问题相同但是规模小于原问题的独立子问题,这就类似于石子合并问题,找到到中间蓝色三角形的代价,然后加上上下部分代价就是原问题的代价了。

所以我们设 d p i , j dp_{i,j} dpi,j表示顶点 i i i j j j多边形中的所有的三角形权值乘积之和的最小值,所以状态转移方程式为:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+w[i]*w[j]*w[k]);

AC代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
long long dp[105][105][35],n,w[105];
void add(long long a[],long long b[]){
    long long c[35];
    memset(c,0,sizeof c);
    int t=0;
    for(int i=0;i<35;i++){
        t+=a[i]+b[i];
        c[i]=t%10;
        t/=10;
    }
    memcpy(a,c,sizeof c);
}
void mul(long long a[],long long b){
    long long c[35];
    memset(c,0,sizeof c);
    long long t=0;
    for(int i=0;i<35;i++){
        t+=a[i]*b;
        c[i]=t%10;
        t/=10;  
    }
    memcpy(a,c,sizeof c);
}
int cmp(long long a[],long long b[]){   
    for(int i=35-1;i>=0;i--)
    if(a[i]>b[i])return 1;
    else if(a[i]<b[i])return -1;
    return 0;
}
void print(long long a[]){
    int k=35-1;
    while(k>=0&&a[k]==0)k--;
    while(k>=0)printf("%lld",a[k--]);
    putchar('\n');
}
int main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
    long long temp[35];
    for(int len=3;len<=n;len++)
        for(int i=1;i+len-1<=n;i++){
            int j=i+len-1;
            dp[i][j][35-1]=1;
            for(int k=i+1;k<j;k++){
                memset(temp,0,sizeof temp);
                temp[0]=w[i];
                mul(temp,w[k]);
                mul(temp,w[j]);
                add(temp,dp[i][k]);
                add(temp,dp[k][j]);
                if(cmp(dp[i][j],temp)>0)memcpy(dp[i][j],temp,sizeof temp);
            }
        }   
    print(dp[1][n]);
    return 0;
}

T4:关路灯

思路:

可以得到两种状态(沿着当前方向继续往下走,改变方向回去关灯)。

我们需要得到的整个关灯过程中的最优值(最小耗能)

那么要设计的状态转移方程中肯定有两种方案(折返的,不撞墙不回头的)

又因为如果想要关某一区间的灯的过程中耗能最小,所以可以转换成一个个区间来写:

去关某一区间的灯,那么整条街道上除了这一区间的灯会逐渐灭掉其他肯定会全亮。

那么我们把 d p i , j dp_{i,j} dpi,j记为当从i到j的灯都熄灭后剩下的灯的总功率。

再进一步: d p i , j , 0 dp_{i,j,0} dpi,j,0表示关掉 i i i j j j的灯后,老张站在i端点, d p i , j , 1 dp_{i,j,1} dpi,j,1表示关掉 i i i j j j的灯后,老张站在右端点

i i i为左端点, j j j为右端点)

d p i , j , 0 dp_{i,j,0} dpi,j,0会由两种方案推导而来(上面有写。):折返回来关 i i i j j j的灯、由 i + 1 i+1 i+1深入,继续关第i盏灯从而扩展到 ( i , j ) (i,j) i,j

所以得到状态转移方程:

	dp[i][j][0]=min(dp[i+1][j][0]+(a[i+1]-a[i])*(pre[n]-pre[j]+pre[i]),dp[i+1][j][1]+(a[j]-a[i])*(pre[n]-pre[j]+pre[i]));
	dp[i][j][1]=min(dp[i][j-1][0]+(a[j]-a[i])*(pre[n]-pre[j-1]+pre[i-1]),dp[i][j-1][1]+(a[j]-a[j-1])*(pre[n]-pre[j-1]+pre[i-1]));

(枚举现在的路灯 l l l 2 − n 2-n 2n,因为第c位的路灯已经被关了), i + l − 1 ⩽ n i+l-1\leqslant n i+l1n(路只有这么长), j = i + l − 1 j=i+l-1 j=i+l1(右端点))

因为最后不知道老张到底站在左端点最优还是站在右端点最优

所以在 d p 1 , n , 0 dp_{1,n,0} dp1,n,0 d p 1 , n , 1 dp_{1,n,1} dp1,n,1中取min输出。

AC代码:

#include<cstdio>
#define min(a,b) a<b?a:b
int n,a[55],dp[55][55][2],pre[55],c,w[55];
int main(){
	scanf("%d %d",&n,&c);
	for(int i=1;i<=n;i++)scanf("%d %d",&a[i],&w[i]),pre[i]=pre[i-1]+w[i];
	for(int i=0;i<=n;i++)
		for(int j=0;j<=n;j++){
			dp[i][j][0]=dp[i][j][1]=0x3f3f3f3f;
			dp[c][c][0]=dp[c][c][1]=0;
		}
	for(int l=2;l<=n;l++){
		for(int i=1;i+l-1<=n;i++){
			int j=i+l-1;
			dp[i][j][0]=min(dp[i+1][j][0]+(a[i+1]-a[i])*(pre[n]-pre[j]+pre[i]),dp[i+1][j][1]+(a[j]-a[i])*(pre[n]-pre[j]+pre[i]));
			dp[i][j][1]=min(dp[i][j-1][0]+(a[j]-a[i])*(pre[n]-pre[j-1]+pre[i-1]),dp[i][j-1][1]+(a[j]-a[j-1])*(pre[n]-pre[j-1]+pre[i-1]));
		}
	}
	printf("%d",min(dp[1][n][0],dp[1][n][1]));
	return 0;
}

T5:金字塔

#### 状态转移方程:
for(int k=i;k<j;k+=2)
	if(s[k]==s[i])dp[i][j]=(dp[i][j]+1ll*dp[i][k]*dp[k+1][j-1])%mod;
			

AC代码:

#include<cstdio>
#include<cstring> 
const int mod=1e9;
int n,dp[305][305];
char s[305];
int main() {
    scanf("%s",s+1);
    int n=strlen(s+1);
    if(n%2==0){
    	printf("0");
    	return 0;
	}
	for(int i=0;i<=n;i++)dp[i][i]=1;
    for (int l=1;l<=n;l+=2)
        for (int i=1;i+l-1<=n;i++) {
            int j=i+l-1;
            if(s[i]==s[j]){
	            for(int k=i;k<j;k+=2)
	                if(s[k]==s[i])dp[i][j]=(dp[i][j]+1ll*dp[i][k]*dp[k+1][j-1])%mod;
			}
        }
    printf("%d",dp[1][n]);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值