区间DP入门

发布时间 2023-11-05 14:11:14作者: lucky_cloud

石子合并

别人讲过太多了,蒟蒻就不说了。

Polygon

这题跟石子合并类似,只是多输出了个先清除哪条边可以使得值最大。

因为我们不确定先删那一条,我们就再复制一遍添到输入的结尾,就变成了 $2 \times N - 1$。

我们思考最大值是由哪些贡献的。

  1. 最大值与最大值运算。
  2. 最小值乘上最小值(因为当负数乘上负数时,会变为正数)。

那么最小值。

  1. 最大值乘上最小值(当最大值为正数时,反而会更小)。
  2. 最小值加上最

我们设 $f1_{i,j}$ 为以 $i$ 为左端点 $j$ 为右端点这个区间内的最大值, $f2_{i,j}$ 为最小值。

定义一个函数:

int deal(int x, int y, int op) {
	if (op == 't') return x + y;
	return x * y;
}

动态转移方程是
$$f1(i,j) = \max(deal(f1(k+1,j), f1(i,k),op_{k+1}),deal(f2(k + 1,j), f2(i,k),op_{k + 1}))$$

$$f2_(i,j) = \min(deal(f2(k+1,j), f2(i,k),op_{k+1}),deal(f1_(k + 1,j), f2_(i,k),op_{k + 1}))$$

最后我们去寻找以 $i$ 为起点的,长度为 $n$ 的最大值,如果等于最后答案,那么便输出,因为这样一定能删去第 $i$ 条边,得到最大值。

串折叠 Folding&秘密文件&[SCOI2003] 字符串折叠

三倍经验,快乐。

就拿第一个讲。

我们设 $dp[i][j]$ 表示字符串中 $s[i\sim j]$ 折叠后的最小长度。

它可以有两种方式转移而来:

  1. 由两段子串折叠后的最小长度加起来。
  2. 以一段子串为循环元,然后出折叠后的长度。

我们通过:

for (int i = l; i + k <= r; i++) {
	if (s[i] != s[i + k]) return 0;
}
return 1;

去找到循环元。

于是:
$$dp[i][j] = min(dp[i][k] + dp[k+1][j], dp[i][k] + 2 + m[(j - i + 1) \div (k - i + 1])$$
其中,$m$ 数组时每个数的位数。因为 $s[i\sim k]$ 为循环元,那么就会有 $(j - i + 1) \div (k - i + 1)$ 个循环元,再加上括号的个数,便为折叠后的个数。

我们还要求出字符串是什么,那么在转移时加上即可。

上代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 200;
int n, f[N][N], m[N];
string s, ans[N][N];

bool check(int l, int r, int k) {
    for (int i = l; i + k <= r; i++) {
        if (s[i] != s[i + k]) return 0;
    }
    return 1;
}

string shu(int x) {//求出数字的字符串
    string res = "";
    while (x) {
        res = char(x % 10 + '0') + res;
        x /= 10;
    }
    return res;
}

int main() {
    for (int i = 1; i <= 9; i++) m[i] = 1;
    for (int i = 10; i < 100; i++) m[i] = 2;
    for (int i = 100; i <= 199; i++) m[i] = 3;
	while (cin >> s) {
		n = s.size();
		s = " " + s;
		for (int l = 0; l < n; l++) {//区间DP
			for (int i = 1; i + l <= n; i++) {
				int j = l + i;
				f[i][j] = l + 1;
                ans[i][j] = s.substr(i, l + 1);//初始化
                for (int k = i; k < j; k++) {
                    if (f[i][j] > f[i][k] + f[k + 1][j]) {
                        f[i][j] = f[i][k] + f[k + 1][j];
                        ans[i][j] = ans[i][k] + ans[k + 1][j];//求出字符串
                    }
                    else if (f[i][j] == f[i][k] + f[k + 1][j]) {
                        ans[i][j] = max(ans[i][j], ans[i][k] + ans[k + 1][j]);//如果个数相同,找到字典序最大的一项。
                    }
                }
				for (int k = i; k < j; k++) {
					int len = k - i + 1;
                    if ((l + 1) % len == 0 && check(i, j, len)) {
                        string sub = shu((l + 1) / len) + '(' + ans[i][k] + ')';//以 s[i~k] 为循环元的字符串
                        if (f[i][j] > f[i][k] + 2 + m[(l + 1) / len]) {
                            f[i][j] = min(f[i][j], f[i][k] + 2 + m[(l + 1) / len]);
                            ans[i][j] = sub;
                        }
                        else if (f[i][j] == f[i][k] + 2 + m[(l + 1) / len]) ans[i][j] = max(ans[i][j], sub);//如果个数相同,找到字典序最大的一项。
                    }
				}
			}
		}
		cout << ans[1][n] << '\n';
	}
	return 0;
}