区间dp总结+例题

参考理解

就是对于区间的一种动态规划,对于某个区间,它的合并方式可能有很多种,我们需要去枚举所有的方式,通常是去枚举区间的分割点,找到最优的方式(一般是找最少消耗)。

个人理解

小区间向大区间递推的过程,在考虑状态转移的时候不必考虑过多的细节,通过题目给的相关性质转移,不 太关心dp怎么来的(跟递归有点像,不关心怎么递归来的,相信程序用就好),直接用就好。状态定义很重要要清楚,准确。如何利用性质分割区间

1.https://www.luogu.com.cn/problem/P4302 字符串折叠
题目描述

给出一个字符串S,可将连续的重复字符串折叠成 X(s) 的形式(可嵌套),表示X个连续字符串s,问S的最短长度。

题解
状态定义:

d p [ i ] [ j ] :区间 [ i , j ] 的最短字符串长度 dp[i][j]:区间[i,j]的最短字符串长度 dp[i][j]:区间[i,j]的最短字符串长度

状态转移:

考虑题目性质,a.当有连续重复的字符串时可以转换成另一种形式

A. 不满足性质a,只能枚举割点来转移
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])
B. 当子区间满足性质a.时需要额外考虑转换的情况,枚举重复子串的前缀分割区间转移
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ k ] + 2 + a [ l e n / s z ] ) ( + 2 为双边括号, a [ l e n / s z ] 为 l e n / s z 数的字符大小) dp[i][j] = min(dp[i][j], dp[i][k] + 2 + a[len / sz]) (+2为双边括号,a[len/sz]为len/sz数的字符大小) dp[i][j]=min(dp[i][j],dp[i][k]+2+a[len/sz])+2为双边括号,a[len/sz]len/sz数的字符大小)

初始化:

长度为1的字符串最短无疑是1了
d p [ i ] [ i ] = 1 dp[i][i]=1 dp[i][i]=1

代码
bool check(int l, int r, int len)
{
    for (int i = l; i <= r; i++)
        if (s[i] != s[(i - l) % len + l])
            return false;
    return true;
}
void solve()
{
    cin >> s;
    n = s.size();
    s = "&" + s;
    for (int i = 1; i <= 9; i++)
        a[i] = 1;
    for (int i = 10; i <= 99; i++)
        a[i] = 2;
    a[100] = 3;
    for (int i = 1; i <= n; i++)
        dp[i][i] = 1;
    for (int len = 2; len <= n; len++)
    {
        for (int i = 1; i + len - 1 <= n; i++)
        {
            int j = i + len - 1;
            dp[i][j] = 1e9;
            for (int k = i; k <= j; k++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
            for (int k = i; k <= j; k++)//[i,k]为重复串
            {
                int sz = k - i + 1;
                if (len % sz)//长度不满足倍数关系
                    continue;
                if (check(i, j, sz))//[i,k]是[i,j]的循环节
                {
                    dp[i][j] = min(dp[i][j], dp[i][k] + 2 + a[len / sz]);
                }
            }
        }
    }
    cout << dp[1][n] << endl;
}
2.https://www.luogu.com.cn/problem/P2470 压缩
题目描述

给一个由小写字母组成的字符串,我们可以用一种简单的方法来压缩其中的重复信息。压缩后的字符串除了小写字母外还可以(但不必)包含大写字母R与M,其中M标记重复串的开始,R重复从上一个M(如果当前位置左边没有M,则从串的开始算起)开始的解压结果(称为缓冲串)。例如bcdcdcdcd 可以压缩为 bMcdRR

题解
状态定义

d p [ i ] [ j ] [ 0 ] :区间 i   j 的还没有加 M 的最小长度 d p [ i ] [ j ] [ 1 ] : 区间 i   j 的已经加了 M 的最小长度 dp[i][j][0]:区间i~j的还没有加M的最小长度\\ dp[i][j][1]: 区间i~j的已经加了M的最小长度 dp[i][j][0]:区间i j的还没有加M的最小长度dp[i][j][1]:区间i j的已经加了M的最小长度

状态转移

考虑性质:a.不加M压缩

​ b.加入M压缩

A.不考虑题目的压缩性质转移:
d p [ i ] [ j ] [ 0 ] = m i n ⁡ ( d p [ i ] [ j ] [ 0 ] , d p [ i ] [ k ] [ 0 ] + j − k ) dp[i][j][0]=min⁡(dp[i][j][0],dp[i][k][0]+j−k) dp[i][j][0]=min(dp[i][j][0],dp[i][k][0]+jk)
B.考虑a.压缩的转移:
d p [ i ] [ j ] [ 0 ] = m i n ⁡ ( d p [ i ] [ j ] [ 0 ] , d p [ i ] [ ( i + j ) / 2 ] + 1 ) dp[i][j][0]=min⁡(dp[i][j][0],dp[i][(i+j)/2]+1) dp[i][j][0]=min(dp[i][j][0],dp[i][(i+j)/2]+1)
C.考虑b.压缩的转移
d p [ i ] [ j ] [ 1 ] = m i n ( d p [ i ] [ j ] [ 1 ] , m i n ( d p [ i ] [ k ] [ 0 ] , d p [ i ] [ k ] [ 1 ] ) + m i n ( d p [ k + 1 ] [ j ] [ 0 ] , d p [ k + 1 ] [ j ] [ 1 ] ) + 1 ) dp[i][j][1] = min(dp[i][j][1], min(dp[i][k][0], dp[i][k][1]) + min(dp[k + 1][j][0], dp[k + 1][j][1]) + 1) dp[i][j][1]=min(dp[i][j][1],min(dp[i][k][0],dp[i][k][1])+min(dp[k+1][j][0],dp[k+1][j][1])+1)

初始化

d p [ i ] [ i ] [ 0 ] = 1 , d p [ i ] [ i ] [ 1 ] = 2 dp[i][i][0] = 1, dp[i][i][1] = 2 dp[i][i][0]=1,dp[i][i][1]=2

代码
bool check(int l, int r) {
    int len = r - l + 1, mid = (l + r) >> 1;
    if (len % 2)
        return false;
    for (int i = 0; i < len / 2; i++)
        if (s[l + i] != s[mid + i + 1])
            return false;
    return true;
}
void solve() {
    cin >> s;
    n = s.size();
    s = "&" + s;
    for (int i = 1; i <= n; i++)
        dp[i][i][0] = 1, dp[i][i][1] = 2;
    for (int len = 2; len <= n; len++) {
        for (int i = 1; i + len - 1 <= n; i++) {
            int j = i + len - 1;
            dp[i][j][0] = dp[i][j][1] = 1e9;
            if (check(i, j))
                dp[i][j][0] = min(dp[i][j][0], dp[i][(i + j) >> 1][0] + 1);
            for (int k = i; k <= j; k++)
                dp[i][j][0] = min(dp[i][j][0], dp[i][k][0] + j - k);
            for (int k = i; k <= j; k++)
                dp[i][j][1] = min(dp[i][j][1], min(dp[i][k][0], dp[i][k][1]) + min(dp[k + 1][j][0], dp[k + 1][j][1]) + 1);
        }
    }
    cout << min(dp[1][n][0], dp[1][n][1]) << "\n";
}
3.https://www.luogu.com.cn/problem/P4766 Outer space invaders
题目描述

N 个外星人进攻,第 i 个进攻的外星人会在时间 a[i] 出现,距离你的距离为 d[i],它必须在时间 b[i] 前被消灭,否则被消灭的会是你。

你的武器是一个区域冲击波器,可以设置任何给定的功率。如果被设置了功率 R,它会瞬间摧毁与你的距离在 R 以内的所有外星人(可以等于),同时它也会消耗 R 单位的燃料电池。

求摧毁所有外星人的最低成本(消耗多少燃料电池),同时保证自己的生命安全。

题解
状态表示

d p [ i ] [ j ] : 消灭在离散化后在 [ i , j ] 内出现的外星人需要的最小消耗 dp[i][j]:消灭在离散化后在[i,j]内出现的外星人需要的最小消耗 dp[i][j]:消灭在离散化后在[i,j]内出现的外星人需要的最小消耗

状态转移

A.区间[i,j]内没有外星人
d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0
B.区间[i,j]内有外星人(考虑题目性质)将区间分割为 [i,k-1]、{k}、[k+1,j]
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ k − 1 ] + d p [ k + 1 ] [ j ] + a [ i d ] . d ) dp[i][j] = min(dp[i][j], dp[i][k - 1] + dp[k + 1][j] + a[id].d) dp[i][j]=min(dp[i][j],dp[i][k1]+dp[k+1][j]+a[id].d)

初始化

初始化为无穷即可

代码
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
sz = v.size() - 1;
for (int i = 1; i <= n; i++)
    a[i].a = raw(a[i].a), a[i].b = raw(a[i].b);//离散化后的相对时间
for (int len = 1; len <= sz; len++) {
    for (int i = 1; i + len - 1 <= sz; i++) {
        int j = i + len - 1;
        dp[i][j] = 1e9;
        int mx = 0, id = 0;
        for (int l = 1; l <= n; l++)
            if (i <= a[l].a && a[l].b <= j && a[l].d > mx)//在[i,j]出现的外星人
                mx = a[l].d, id = l;
        if (mx == 0)
            dp[i][j] = 0;
        else {
            for (int k = a[id].a; k <= a[id].b; k++) {
                dp[i][j] = min(dp[i][j], dp[i][k - 1] + dp[k + 1][j] + a[id].d);
            }
        }
    }
}
cout << dp[1][sz] <<"\n";
4.https://acm.hdu.edu.cn/showproblem.php?pid=2476 string painter
题目描述

给定两个长度相等的字符串A、B,由小写字母组成。一次操作允许把A的一个连续子串都转换成同一个字符。将A转化成B,需要的最小操作次数是多少。

题解

先考虑一个空串转换成B需要的最小操作次数

状态定义:

d p [ i ] [ j ] : 将空串的区间 [ i , j ] 转换成 B 需要的最小操作次数 dp[i][j]:将空串的区间[i,j]转换成B需要的最小操作次数 dp[i][j]:将空串的区间[i,j]转换成B需要的最小操作次数

状态转移

A.不考虑性质转移
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])
B.考虑性质转移(连续刷一个子串)
d p [ i ] [ j ] = d p [ i + 1 ] [ j ] 如果 b [ i ] = = b [ j ] dp[i][j]=dp[i+1][j] 如果b[i]==b[j] dp[i][j]=dp[i+1][j]如果b[i]==b[j]
再考虑原串相对于空串能够减少的操作次数

如果a[i] = b[i]:
d p [ 1 ] [ i ] = d p [ 1 ] [ i − 1 ] dp[1][i]=dp[1][i-1] dp[1][i]=dp[1][i1]
如果a[i] ≠ b[i]:
d p [ 1 ] [ i ] = m i n ( d p [ 1 ] [ i ] , d p [ 1 ] [ j ] + d p [ j + 1 ] [ i ] ) (枚举断点分割区间) dp[1][i]=min(dp[1][i],dp[1][j]+dp[j+1][i])(枚举断点分割区间) dp[1][i]=min(dp[1][i],dp[1][j]+dp[j+1][i])(枚举断点分割区间)

初始化

区间大小为 1 直接用一次操作即可: d p [ i ] [ i ] = 1 区间大小为1直接用一次操作即可:dp[i][i]=1 区间大小为1直接用一次操作即可:dp[i][i]=1

代码
n = a.size();
a = "&" + a, b = "&" + b;
for (int i = 1; i <= n; i++)
    dp[i][i] = 1;
for (int len = 2; len <= n; len++) {
    for (int i = 1; i <= n; i++) {
        int j = i + len - 1;
        dp[i][j] = 1e9;
        if (b[i] == b[j])
            dp[i][j] = dp[i][j - 1];
        else
            for (int k = i; k <= j; k++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
    }
}
for (int i = 1; i <= n; i++) {
    if (a[i] == b[i])
        dp[1][i] = dp[1][i - 1];
    else
        for (int j = 1; j <= i; j++)
            dp[1][i] = min(dp[1][i], dp[1][j] + dp[j + 1][i]);
}
cout << dp[1][n] << "\n";
5.https://codeforces.com/contest/1763/problem/D D. Valid Bitonic Permutations
题目描述

给出5个数n,i,j,x,y分别代表排列的大小、a[i]=x,a[j]=y。问满足条件的严格先单调增再单调减的排列的个数。

题解
状态定义

d p [ i ] [ j ] : 填完区间 [ i , j ] 的满足条件的方案数 dp[i][j]:填完区间[i,j]的满足条件的方案数 dp[i][j]:填完区间[i,j]的满足条件的方案数

状态转移

分析题目性质:a.需要满足严格单调增再单调减

​ b.要满足a[i]=x,a[j]=y.

可以从性质a入手,既然要满足单调性,那我们就通过一次将数从大到小填的方式,从小区间到大区间去转移,分割区间。

则有状态转移:

如果当前的最大数能填在i:
d p [ i ] [ j ] = d p [ i + 1 ] [ j ] + d p [ i ] [ j ] dp[i][j]=dp[i+1][j]+dp[i][j] dp[i][j]=dp[i+1][j]+dp[i][j]
如果当前的最大数能填在j:
d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + d p [ i ] [ j ] dp[i][j]=dp[i][j-1]+dp[i][j] dp[i][j]=dp[i][j1]+dp[i][j]

初始化

d p [ i ] [ i ] = 1 ; ( 将 n 填在 i 位置处,且不与性质 b 冲突 ) dp[i][i]=1;(将n填在i位置处,且不与性质b冲突) dp[i][i]=1;(n填在i位置处,且不与性质b冲突)

代码
int n, u, v, x, y, a[N], dp[110][110];
bool check(int id, int val) {
    if (id == u && val != x)
        return false;
    if (id == v && val != y)
        return false;
    return true;
}
void solve() {
    memset(dp, 0, sizeof dp);
    cin >> n >> u >> v >> x >> y;
    for (int i = 2; i < n; i++)
        if (check(i, n))
            dp[i][i] = 1;
    for (int i = n; i >= 1; i--) {
        for (int j = i; j <= n; j++) {
            if (check(i, n - (j - i)))
                dp[i][j] = (dp[i][j] + dp[i + 1][j]) % mod;
            if (check(j, n - (j - i)))
                dp[i][j] = (dp[i][j] + dp[i][j - 1]) % mod;
        }
    }
    cout << dp[1][n] << endl;
}

个人感觉还是得多练,状态定义和转移才能更熟悉、准确。
其它题目

http://oj.daimayuan.top/course/5/problem/200

http://oj.daimayuan.top/course/5/problem/202

https://acm.hdu.edu.cn/showproblem.php?pid=4283

https://www.luogu.com.cn/problem/P4342

https://codeforces.com/contest/1767/problem/C

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

self_disc

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

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

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

打赏作者

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

抵扣说明:

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

余额充值