【备战NOIP】专题复习2-动态规划-区间DP

【备战NOIP】专题复习2-动态规划-区间DP

区间DP模板

题目链接:石子合并(弱化版)

题意:设有 N ( N ≤ 300 ) N(N \le 300) N(N300) 堆石子排成一排,其编号为 1 , 2 , 3 , ⋯   , N 1,2,3,\cdots,N 1,2,3,,N。每堆石子有一定的质量 m i ( m i ≤ 1000 ) m_i(m_i \le 1000) mi(mi1000)。现在要将这 N N N 堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻。合并时由于选择的顺序不同,合并的总代价也不相同。试找出一种合理的方法,使总的代价最小,并输出最小代价。

解析:设 d p [ i ] [ j ] dp[i][j] dp[i][j]为合并区间 [ i , j ] [i,j] [i,j]石子的最优解,则有 d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + s u m ) ; dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum); dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum);代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=3e2+5;
int n,a[N],dp[N][N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;i++) dp[i][i]=0;
	for(int l=2;l<=n;l++)
		for(int i=1;i<=n-l+1;i++)
		{
			int j=i+l-1;
			int sum=0;
			for(int k=i;k<=j;k++) sum+=a[k];
			for(int k=i;k<j;k++)
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum);
		}
	cout<<dp[1][n]<<endl;
	return 0;
}

环形区间DP

题目链接:石子合并

题意:在一个圆形操场的四周摆放 N N N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 2 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。试设计出一个算法,计算出将 N N N 堆石子合并成 1 1 1 堆的最小得分和最大得分。

解析:断链成环,具体操作是将原数组复制成两倍,然后跟上题一样做区间 D P DP DP即可,代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,m,k;
int a[N],dp[N][N],dp1[N][N],s[N];
int main()
{
	cin>>n;
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;i++) cin>>a[i],a[n+i]=a[i];
	for(int i=1;i<=2*n;i++) dp[i][i]=0,dp1[i][i]=0,s[i]=s[i-1]+a[i];
	for(int len=1;len<=n;len++)
	{
		for(int i=1;i<=2*n-len+1;i++)
		{
			int j=i+len-1;
			for(int k=i;k<j;k++)
			{
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]); 
				dp1[i][j]=max(dp1[i][j],dp1[i][k]+dp1[k+1][j]+s[j]-s[i-1]);
			}
		}
	}
	int ans=0x3f3f3f3f,ans1=-1;
	for(int i=1;i<=n;i++) ans=min(ans,dp[i][i+n-1]),ans1=max(ans1,dp1[i][i+n-1]);
	cout<<ans<<endl;
	cout<<ans1<<endl;
	return 0;
}

题目链接:能量项链

题意:给你一项链,项链上有 n n n颗珠子。相邻的两颗珠子可以合并(两个合并成一个)。合并的同时会放出一定的能量。不同的珠子的合并所释放的能量是不同的。问:按照怎样的次序合并才能使释放的能量最多?

解析:根据区间 D P DP DP的套路,易得状态转移方程为: d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + a [ i ] ∗ a [ k + 1 ] ∗ a [ j + 1 ] ) ; dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]*a[k+1]*a[j+1]); dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]a[k+1]a[j+1]);跟上题一样,断链成环,做一遍 D P DP DP即可。代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,m,k;
int a[N],dp[N][N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i];a[2*n+1]=a[1];
	for(int i=1;i<=2*n;i++) dp[i][i]=0;
	for(int l=2;l<=n;l++)
		for(int i=1;i<=2*n-l+1;i++)
		{
			int j=i+l-1;
			for(int k=i;k<j;k++)
				dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]*a[k+1]*a[j+1]);
		}
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,dp[i][i+n-1]);
	cout<<ans<<endl;
	return 0;
}

区间DP的一些应用

题目链接:涂色

题意:假设你有一条长度为 5 5 5 的木板,初始时没有涂过任何颜色。你希望把它的 5 5 5 个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为 5 5 5 的字符串表示这个目标: RGBGR \texttt{RGBGR} RGBGR。每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。例如第一次把木板涂成 RRRRR \texttt{RRRRR} RRRRR,第二次涂成 RGGGR \texttt{RGGGR} RGGGR,第三次涂成 RGBGR \texttt{RGBGR} RGBGR,达到目标。用尽量少的涂色次数达到目标。

解析:本题需要小小地贪心一些,当 s [ i ] = = s [ j ] s[i]==s[j] s[i]==s[j]时,要么让区间 [ i , j − 1 ] [i,j-1] [i,j1] i i i的时候多往右刷一格,要么让区间 [ i + 1 , j ] [i+1,j] [i+1,j] j j j的时候多往左刷一格,这样不会产生更多的代价且多刷了一格,显然只会使得结果更优。不相等的话,只能裂开取最小值了。代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,k;
int a[N],dp[N][N];
char s[N];
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    memset(dp,0x3f,sizeof(dp));
    for(int i=1;i<=n;i++) dp[i][i]=1;
    for(int l=2;l<=n;l++)
        for(int i=1;i<=n;i++)
        {
            int j=i+l-1;
            if(s[i]==s[j]) dp[i][j]=min(dp[i][j-1],dp[i+1][j]);
            else
            {
            	for(int k=i;k<j;k++)
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
			}
        }
    cout<<dp[1][n]<<endl;
    return 0;
}

题目链接:248 G

题意:给定一个1*n的地图,在里面玩2048,每次可以合并相邻两个(数值范围1-40),问序列中出现的最大数字的值最大是多少。注意合并后的数值并非加倍而是+1,例如2与2合并后的数值为3。

解析:区间 D P DP DP,裂开取最大值即可,注意需要满足两块相等才能合并。代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=255;
int n,m,k;
int a[N],dp[N][N];
int main(){
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i],dp[i][i]=a[i];
        for(int l=2;l<=n;l++)
                for(int i=1;i<=n-l+1;i++)
                {
                        int j=i+l-1;
                        for(int k=i;k<j;k++)
                            if(dp[i][k]==dp[k+1][j]) dp[i][j]=max(dp[i][j],dp[i][k]+1);
                }
        int ans=0;
        for(int i=1;i<=n;i++)
                for(int j=i;j<=n;j++)
                        ans=max(ans,dp[i][j]);
        cout<<ans<<endl;
        return 0;
}

题目链接:字符串折叠

题意:折叠的定义如下:

  1. 一个字符串可以看成它自身的折叠。记作 S = S

  2. X(S) X X XS连接在一起的串的折叠。记作 X(S) = SSSS…S

  3. 如果 A = A’, B = B’,则 AB = A’B’ 。例如:因为 3(A) = AAA, 2(B) = BB,所以 3(A)C2(B) = AAACBB,而 2(3(A)C)2(B) = AAACAAACBB

给一个字符串,求它的最短折叠。

例如 AAAAAAAAAABABABCCD的最短折叠为:9(A)3(AB)CCD

解析:考虑区间动态规划,先分类讨论,对于区间 [ i , j ] [i,j] [i,j]对应的字符串可以不采用缩写的方式,那么有 d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k + 1 ] [ j ] ) ; dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);

如果对区间 [ i , j ] [i,j] [i,j]对应的字符串采用缩写的方式,那么枚举循环节,假设 [ i , k ] [i,k] [i,k]对应的字符串是 [ i , j ] [i,j] [i,j]对应的字符串的循环节,那么有

$dp[i][j]=min(dp[i][j],dp[i][k]+2+len);//2是括号长度再加数字长度 $ 代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,k;
int a[N],dp[N][N];
string s;
int main(){
    cin>>s;
    n=s.size();
    memset(dp,0x3f,sizeof(dp));
    s="0"+s;
    for(int i=1;i<=n;i++) dp[i][i]=1;
    for(int l=2;l<=n;l++)
        for(int i=1;i<=n-l+1;i++)
        {
            int j=i+l-1;
            for(int k=i;k<j;k++)//不使用缩写 
            	dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
            string all=s.substr(i,j-i+1);
          	//cout<<i<<"->"<<j<<all<<endl;
            for(int k=i;k<j;k++)//使用缩写 枚举循环节,并判断s[i...k]是否是s[i...j]的循环节 
            {
                if(l%(k-i+1)!=0) continue;
                string mode=s.substr(i,k-i+1),tmp="";
				//cout<<"mode = "<<mode<<endl;
                while(tmp.size()<all.size()) tmp+=mode;
                if(tmp==all)//是循环节 
                {
                    int x=l/(k-i+1),len;
                    if(x<10) len=1;
                    else if(x<100) len=2;
                    else len=3;
                    dp[i][j]=min(dp[i][j],dp[i][k]+2+len);//2是括号长度再加数字长度 
                }
            }
//                        printf("dp[%d][%d]=%d\n",i,j,dp[i][j]); 
        }
    cout<<dp[1][n]<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值