传送门

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\) 方程为:

\[f[i][j] = \min\{ f[i-1][k] + |j-h_i| \times c_i \} \]

由于没有第 \(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 方程可以转成:

\[f[i][j] = \min\{ f[i-1][k] \} + |j-h_i| \times c_i \]

\(k\) 的取值范围是 \([j-x, j+x]\),而优先枚举 \(i\) 的情况下,\(f[i-1][k]\) 已经全部求出来了,因此我们可以使用优先队列维护规定区间内最大的 \(f[i-1][k]\),然后直接更新 \(f[i][j]\)
时间复杂度为 \(O(nH\log H)\)