P10161 [DTCPC 2024] 小方的疑惑 10
Solution
-
一开始看这题的时候,我们可能会觉得无从下手,这时不妨列出几种方案,计算它们的贡献,尝试得到一些启发。
-
画来画去,发现无非就是并列和包含两种情况,并列就是 ()()()(),设它一共由 \(x\) 对括号组成,那么它的总贡献是 \(x\times (x+1)\div 2\)。包含就是 (...),这种情况下我们每在外面添加一对括号,贡献就会加一。
-
欸?既然加一都有了,那如果不考虑长度 \(n\),无论如何我们都能凑够恰好 \(k\) 个合法括号子串。所以现在我们要考虑怎么样构造这个串,使得它长度在 \(n\) 之内,如果不足 \(n\),剩下我们随便放几个左括号补上就可以了。
-
现在还是有个问题,我们直接判断合不合法比较困难。对于这种情况,可以考虑求最小长度,如果最小长度都大于 \(n\),那么肯定就不合法了。
-
如何构造最优答案呢。我们思考一件事情,它要求最小答案,然后这些答案产生的贡献要恰好等于 \(k\),那这很可能是个背包啊。\(k\) 就是背包的容量,对于一个有 \(y\) 对括号的并列串,它的价值是 \(2\times y\),重量是 \(y\times (y+1)\div 2\),然后就相当于是完全背包求最小价值。
-
那如果两个并列串直接合起来,肯定是不行的,因为贡献会跨串计算。例如我们要放两件物品 ()()() 和 [][][],直接合并 ()()()[][][] 的话,贡献远不止 \(3\times (3+1)\div 2+3\times (3+1)\div 2\),如果我们这样子呢 [()()()][][],它的贡献刚好就是 \(3\times (3+1)\div 2+3\times (3+1)\div 2\),这是因为第一个中括号把里面的贡献和外面隔绝了。至此,各个物品的重量是可以直接累加的,价值也是可以直接累加的,于是我们进行一遍完全背包即可。
-
为什么这样构造是最优的呢?感性理解一下,这样构造首先它没有一个多余的括号,其次,对于其他任何情况,都可以等价转换为这种方案。
-
输出方案的话,只需要对于每个状态,记录转移过来的是哪个状态即可,具体见代码。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;const int N = 1e5 + 5, inf = 1e9;int n, k, w[N], dp[N], pre[N];void print(int now) {if (!now) return;int cnt = (dp[now] - dp[pre[now]]) / 2;cout << "(";print(pre[now]);cout << ")";for (int i = 1; i < cnt; i++) cout << "()";
}void Solve() {cin >> n >> k;if (dp[k] > n) cout << "-1\n";else {print(k);for (int i = 1; i <= n - dp[k]; i++) cout << "(";cout << "\n";}
}int main() {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);for (int i = 1; i <= 1000; i++) w[i] = i * (i + 1) / 2;fill(dp + 1, dp + 100000, inf);dp[0] = 0;for (int i = 1; i <= 1000; i++) {for (int j = w[i]; j <= 100000; j++) {if (dp[j - w[i]] + i * 2 < dp[j]) {dp[j] = dp[j - w[i]] + i * 2;pre[j] = j - w[i];}}}int T;cin >> T;while (T--) Solve();return 0;
}```