ACwing算法备战蓝桥杯——Day28——区间DP

石子合并:

思路:

状态表示:用二维状态f[i][j]表示合并成一个区间 [i,j] 的耗费最小值

状态计算:将区间划分成更小的子区间

总共三层循环:

第一层为区间长度:因为每个区间都是由更短的区间得来,而枚举区间长度就能这样计算。

第二层为区间的起始坐标:这样就确定了唯一一个区间,并且保证不重不漏;

第三层为分割区间的位置。

ps:一开始前两层我是从前往后枚举区间的左右端点坐标,这样做很显然是错的,因为这样做的话计算的逻辑不是从最小的集合开始逐步推出大集合,也就是说在计算大集合时,划分出来的小集合有的还没有被计算过。

所有DP问题都是从前一个状态推出现在的状态

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1、2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;

如果第二步是先合并 2、3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式
第一行一个数 N 表示石子的堆数 N。

第二行 N 个数,表示每堆石子的质量(均不超过 1000)。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤N≤300
输入样例:
4
1 3 5 2
输出样例:
22

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N=310;

int s[N],f[N][N];

int main(){
    
    int n;
    cin>>n;
    
    for(int i=1;i<=n;i++){
        cin>>s[i];
        s[i]+=s[i-1];
    }
    
    memset(f,0x3f,sizeof f);//表示不合法,注意理解
    
    for(int i=1;i<=n;i++) f[i][i]=0;//只有一个石头不合并
    
    
    for(int len=1;len<=n;len++){
        for(int i=1;i+len-1<=n;i++){//枚举每个区间
            int j=i+len-1;
            
            //当只有一个石子时,花费为0
            if(len==1){
                f[i][j]=0;
                continue;
            }
            
            int MIN=1e9;
            
            for(int k=i;k+1<=j;k++){
                MIN=min(MIN,f[i][k]+f[k+1][j]);
            }
            
            f[i][j]=MIN+s[j]-s[i-1];
            
        }
    }
    
    cout<<f[1][n]<<endl;
    return 0;
}

涂色:

难题;

有 n 个砖块排成一排,从左到右编号为 1∼n。

其中,第 i 个砖块的初始颜色为 ci。

我们规定,如果编号范围 [i,j] 内的所有砖块的颜色都相同,且当第 i−1 和 第 j+1 个砖块存在时,这两个砖块的颜色和区间 [i,j] 的颜色均不同, 则砖块 i 和 j 属于同一个连通块。

例如,[3,3,3] 有 1 个连通块,[5,2,4,4] 有 3 个连通块。

现在,要对砖块进行涂色操作。

开始所有操作之前,你需要任选一个砖块作为起始砖块。

每次操作:

任选一种颜色。
将最开始选定的起始砖块所在连通块中包含的所有砖块都涂为选定颜色,
请问,至少需要多少次操作,才能使所有砖块都具有同一种颜色。

输入格式
第一行包含整数 n。

第二行包含 n 个整数 c1,c2,…,cn。

输出格式
一个整数,表示所需要的最少操作次数。

数据范围
前六个测试点满足,1≤n≤20。
所有测试点满足,1≤n≤5000,1≤ci≤5000。

输入样例1:
4
5 2 2 1
输出样例1:
2
输入样例2:
8
4 5 2 2 1 3 5 5
输出样例2:
4
输入样例3:
1
4
输出样例3:
0
输入样例4:
5
1 3 1 2 1
输出样例4:
3
样例4解释
注意,每次染色操作所涉及的连通块必须包含所有操作开始前选定的起始砖块。

因此,答案是 3,而不是 2。

代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std ;
const int N = 5010 ;
int n ;
int a[N] , f[N][N] ;

int main()
{
    cin >> n ;
    for(int i = 0 ; i < n ; i ++) 
    {
        scanf("%d",&a[i]) ;
        if(i && a[i] == a[i - 1])
        {
            i -- ;
            n -- ;
        }
    }
    for(int len = 2 ; len <= n ; len ++)
    {
        for(int i = 0 ; i + len - 1 < n ; i ++)
        {
            int j = i + len - 1 ;
            if(a[i] != a[j]) f[i][j] = min(f[i + 1][j] , f[i][j - 1]) + 1 ;
            else f[i][j] = f[i + 1][j - 1] + 1 ;
        }
    }
    cout << f[0][n - 1] << endl ;
    return 0 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

切勿踌躇不前

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值