T2. 括号分配
给定一个由括号构成的字符串(长度 \(n \leqslant 1000\)),把它分为两个子序列,子序列均是匹配的括号序列且不重复不遗漏,符合要求的划分方案数。
算法分析
30分:
当 \(n \leqslant 20\) 时,可以使用 \(\mathrm{dfs}\) 枚举每个字符属于哪个子序列,然后扫描检查两个子序列是否是匹配的括号序列,时间复杂度为 \(O(2^n \times n)\) 。
60分:
当 \(n \leqslant 500\) 时,可以考虑 \(O(n^3)\) 算法。括号序列可以用一个变量判断是否匹配,代码如下:
bool check(string s) {
int t = 0;
rep(i, s.size()) {
if (s[i] == '(') t++;
else t--;
if (t < 0) return 0;
}
return t == 0;
}
正确的括号序列需要左括号数量一直大于等于右括号数量,也就是左括号个数减右括号个数的差值 \(t \geqslant 0\),最终 \(t = 0\)。我们可以只维护两个子序列分别的差值,然后确保差值非负的情况下计算方案数。
设 dp[i][j][k] 表示前 \(i\) 个字符分配给两个子序列,第一个子序列当前的差值为 \(j\),第二个子序列当前的差值为 \(k\),对应的方案数。那么 \(dp\) 初始状态为 \(dp[0][0][0]=1\),\(dp\) 的目标状态 \(dp[n][0][0]\) 为最终答案。
枚举 \(i, j, k\)(注意 \(0 \leqslant j, k \leqslant i\)),然后根据第 \(i\) 个字符 \(s_i\) 的值,进行状态转移:
当 \(s_i\) 为左括号时,分配 \(s_i\) 会使得差值增大,因此 \(dp[i][j][k]\) 可以来自于 \(dp[i-1][j-1][k]\) 或者 \(dp[i-1][j][k-1]\) 。
当 \(s_i\) 为右括号时,分配 \(s_i\) 会使得差值减少,因此 \(dp[i][j][k]\) 可以来自于 \(dp[i-1][j+1][k]\) 或者 \(dp[i-1][j][k+1]\) 。
100分:
在 \(60\) 分的dp中,考虑继续优化。由于所有字符都要分配,因此 \(j\) 和 \(k\) 符合某种规则,不需要枚举 \(k\)。
假设前 \(i\) 个字符中有 \(a\) 个左括号、\(b\) 个右括号,然后 \(x\) 个左括号、\(y\) 个右括号分配给了第一个子序列,那么 \(a-x\) 个左括号、\(b-y\) 个右括号分配给了第二个子序列。
此时 \(j=x-y\),\(k=(a-x)-(b-y)=a-b-x+y=a-b-x+y=a-b-j\) 。
设 \(t_i\) 表示前 \(i\) 个字符中左括号个数减右括号个数的差值,发现 \(k=t_i-j\)。
枚举 \(dp[i][j]\) —— 前 \(i\) 个字符分配给两个子序列,第一个子序列当前的差值为 \(j\),那么第二个子序列的差值为 \(t_i-j\)。
当 \(s_i\) 为左括号时,分配 \(s_i\) 会使得差值增大,如果 \(j > 0\),dp[i][j] += dp[i-1][j-1];如果 \(t_i-j > 0\),dp[i][j] += dp[i-1][j]
当 \(s_i\) 为右括号时,分配 \(s_i\) 会使得差值减少,因此 dp[i][j] += dp[i-1][j+1] + dp[i-1][j]。
初值为 \(dp[0][0]=1\),答案为 \(dp[n][0]\)。时间复杂度是 \(O(n^2)\)。
T3. 最优灯柱
给定一个数组 \(h_i\),在预算 \(M\) 内,通过调整 \(h_i\) 的大小(第 \(i\) 个数字每次 \(+1\) 或者 \(-1\) 的代价是 \(c_i\))使得相邻数字差值的绝对值最大值尽可能小。
限制:
- 对于 \(15\%\) 的数据,\(1 \leqslant N \leqslant 6\),\(0 \leqslant h_i \leqslant 10\)
- 对于 \(35\%\) 的数据,\(1 \leqslant N \leqslant 100\),\(0 \leqslant h_i \leqslant 300\)
- 对于 \(70\%\) 的数据,\(1 \leqslant N \leqslant 500\),\(0 \leqslant h_i \leqslant 1000\)
- 对于 \(100\%\) 的数据,\(1 \leqslant N \leqslant 800\),\(1 \leqslant M \leqslant 2 \times 10^7\),\(0 \leqslant h_i \leqslant 2000\),\(0 \leqslant c_i \leqslant 1000\)
算法分析
15分:
由于数据范围很小,可以直接枚举每个数字最终的值,然后检查是否在预算 \(M\) 内。
35分:
显然预算越多,差值越小,符合二分性质。
二分最终差值的绝对值 \(x\),然后计算使得差值 \(\leqslant x\) 的最小花费 \(s\),如果 \(s \leqslant M\),那么 \(x\) 成立。
在检查 \(x\) 时,我们需要使用 \(dp\),为了计算差值,必须把最后一根灯柱存下来。设 f[i][j] 表示前 \(i\) 根灯柱,第 \(i\) 根灯柱的高度为 \(j\),差值不超过 \(x\) 时对应的最小花费。
枚举第 \(i-1\) 根灯柱的高度 \(k(j-x \leqslant k \leqslant j+x)\),\(dp\) 方程为:
由于没有第 \(0\) 根灯柱,因此初始状态为 \(f[i][j] = |j-h_1| \times c_1\),答案为 \(\min\{f[n][0 \sim \mathrm{maxh}]\}\)
时间复杂度为 \(O(nH^2\log H)\),其中 \(H=\mathrm{maxh}\)
100分:
在 \(35\) 分的基础上,我们考虑对 dp 进行优化。之前的 dp 方程可以转成:
\(k\) 的取值范围是 \([j-x, j+x]\),而优先枚举 \(i\) 的情况下,\(f[i-1][k]\) 已经全部求出来了,因此我们可以使用优先队列维护规定区间内最大的 \(f[i-1][k]\),然后直接更新 \(f[i][j]\)。
时间复杂度为 \(O(nH\log H)\)。
浙公网安备 33010602011771号