参考理解
就是对于区间的一种动态规划,对于某个区间,它的合并方式可能有很多种,我们需要去枚举所有的方式,通常是去枚举区间的分割点,找到最优的方式(一般是找最少消耗)。
个人理解
小区间向大区间递推的过程,在考虑状态转移的时候不必考虑过多的细节,通过题目给的相关性质转移,不 太关心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]+j−k)
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][k−1]+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][i−1]
如果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][j−1]+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