建筑升级
首先看到 就很不能让人不想到 的 dp。dp 状态中,第一位表示长度为 的建筑前缀是必须的,那么第二维是什么呢?由于最终获得的额外分数和所有建筑的最小级别有关系,因此第二维就存前缀中所有建筑中最低的级别。
设 表示在前 个建筑中,等级最小为 ,最多能得多少分。那么对于新的一个建筑,假设我们选择让这个建筑升级到 级,那么有以下两种情况:
- 如果 ,那么最低等级 不会改变;
- 如果 ,那么最低等级 会变成 。
因此假设当前转移到 ,那么有两种方式,一种是从最小值比当前大的状态,但是当前选择的等级就是 的转移;另一种是最小值和当前一样,那么当前的等级就可以是任意比之前最小值大的而等级。
#include <iostream>
using namespace std;
using LL = long long;
const LL kMaxN = 1005;
LL a[kMaxN][kMaxN], dp[kMaxN][kMaxN], d[kMaxN], t, n, m, ans;
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n >> m;
for (LL i = 1; i <= n; i++) {
for (LL j = 1; j <= m; j++) {
cin >> a[i][j];
a[i][j] += a[i][j - 1];
}
}
for (LL i = 0; i <= m; i++) {
cin >> d[i];
if (i >= 1) {
d[i] += d[i - 1];
}
}
fill(dp[0], dp[0] + m, -1e18);
dp[0][m] = 0;
for (LL i = 1; i <= n; i++) {
for (LL j = m, pre = -1e18, mx = a[i][j]; j >= 0; j--) {
mx = max(mx, a[i][j]);
dp[i][j] = max(dp[i - 1][j] + mx, pre + a[i][j]); // 最低等级不变或降低最低等级
pre = max(pre, dp[i - 1][j]);
}
}
ans = -1e18;
for (LL i = 0; i <= m; i++) {
ans = max(ans, dp[n][i] + d[i]);
}
cout << ans << '\n';
}
return 0;
}快速蜗蜗变换
最开始我想到的是 01 trie,但是后面发现不能考虑到所有的情况。
最后还是写了个子集枚举,即对于每个 枚举 的二进制的子集,然后把 和 存入所有 的子集的值中间,并且分别取最大、最小值,然后倒序枚举 ,把之前的最大、最小 值取出来以四种情况中取最大值,然后再做一个后缀 ,累加即可。
正确性证明:如果 且 ,那么必然有 ,因为 有的每一个 都在 和 里面出现过,此时 ,然后我们再做一个后缀和就可以覆盖所有的 的情况。
时间复杂度高达 ,当 时大约是 左右,但是可以直接卡过。其实正解和这个区别不大,这个是枚举出所有的子集,正解是每次从已有的 中间掏去一个。
#include <iostream>
#include <algorithm>
using namespace std;
using LL = long long;
const LL kMaxN = 2e5 + 5, kMod = 998244353;
LL a[kMaxN], b[kMaxN], mxa[kMaxN], mxb[kMaxN], mna[kMaxN], mnb[kMaxN], t, n, ans, mx;
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n;
for (LL i = 0; i < n; i++) {
cin >> a[i];
}
for (LL i = 0; i < n; i++) {
cin >> b[i];
mna[i] = mnb[i] = 1e18;
mxa[i] = mxb[i] = -1e18;
}
for (LL i = 0; i < n; i++) {
for (LL x = i; x; x = (x - 1) & i) {
mxa[x] = max(mxa[x], a[i]);
mna[x] = min(mna[x], a[i]);
mxb[x] = max(mxb[x], b[i]);
mnb[x] = min(mnb[x], b[i]);
}
mxa[0] = max(mxa[0], a[i]);
mna[0] = min(mna[0], a[i]);
mxb[0] = max(mxb[0], b[i]);
mnb[0] = min(mnb[0], b[i]);
}
ans = 0;
mx = -1e18;
for (LL i = n - 1; i >= 0; i--) {
mx = max(mx, max(
max(mna[i] * mnb[i], mna[i] * mxb[i]),
max(mxa[i] * mnb[i], mxa[i] * mxb[i])));
ans = ((ans + mx) % kMod + kMod) % kMod;
}
cout << ans << '\n';
}
return 0;
}互不相同
我使用的是暴力。
对于子任务 3,可以采用迭代加深搜索,由于答案大概在 左右,因此最多也就迭代 次,单次瞎选一个区间,然后加一个特别大且特殊的值(我是 , 是当前深度,二进制保证不会重复),如果满足条件就可以退出了。这个搜索可以帮助我们找出子任务 1、2 的规律。
对于子任务 1,全部 都是 ,使用我们的搜索不难得到
对于子任务 2,我的式子猜错了,导致 wa 最后一个子测试点。
运货
我的思路是错的。
我最开始想的是二分,二分出一个答案进行 判断,时间复杂度 ,但是答案根本没有单调性(如 6 3 2, 如果承重为 可以带 个,但是当承重为 时只能带 个),于是趋势。
总结
分数 。
下次写题目的时候不能看着好像可以二分就写二分,应该仔细判断单调性。然后更改做题侧重点,应该把重点放在第二题和第三题上而非第四题。