区间DP【5.8】

区间DP

T1:玩雪的小Y

就是能量项链嘛(大雾弥漫

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

AC代码:

#include
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
#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 
#include 
#include 
#include 
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
#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
#include 
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;
}

你可能感兴趣的:(动态规划,算法,c++)