CSPS-2024补题笔记--染色
T3 染色
首先, 我们可以发现, 只有当颜色相同的时候我们的当前元素才会产生奉献, 这启示我们可以在后面用一个桶(\(a_i\) 的规模不超过 \(10 ^ 6\))来记录上一次当前值出现的位置。
既然我们现在有对于值相同情况时的记录了, 然后我们可以发现, 对于每一个位置, 我们其实产生影响的只有上一个和当前同色并且是\(lst_{a_i}\)的东西, 然后我们需要考虑如果说当前换了和前面比的话会不会现在更劣, 但是在后面逆转为更好的呢?
对于这一个问题, 我们可以先放在一旁, 首先先开始写公式
首先我们可以知道, 如果说是下图的情况, 我们中间的奉献就是中间相同值的一个和, 对于这个东西, 我们可以提前预处理出来中间产生的奉献, 此处记作\(s_i\)为到当前位置这个相邻元素产生的奉献的前缀和, 以便于后面进行区间的计算

然后我们就可以发现, 对于前面所说的当前情况更劣然后转换为好的话, 那我们后面的值是在前面的值上面进行计算的, 而前面的值越大, 对于后面我们的转移会更有利。
而对于把上一个和自己相同值的转换为和自己同色的话, 我们需要把关于那个值所产生的奉献给删除, 然后再加上当前这段区间的值(因为对于上一个值的前面的不同色的, 我们已经存在动规数组\(f_i\)中了, 不需要重新计算)
这样的话, 我们就有dp方程:
\[f_i = max(f_{i - 1}, f_{lst_{a_i} + 1} + a_i + s_i - s_{lst_{a_i} + 1})
\]
同时, 在每次计算完当前的值后, 就可以把当前的\(lst_{a_i}\)更新为当前位置, 因为每一次我们所需要的都是最近一个的同值位置
然后这个公式的解读如下:
- 对于\(max\), 我们需要把当前换和不换进行比较, 选择更优的替换, 这很容易理解
- 对于$ f_{lst_{a_i} + 1}\(, 就是在上一个同值的元素后一个位置, 也就是要包含\)lst_{a_i}$前面的结果
- 对于\(a_i\), 就是当前会产生的奉献
- 对于\(s_i - s_{lst_{a_i} + 1}\), 就是对于当前值和上一个相同值的中间所产生的奉献区间和
这样的话, 这道题我们就做完了, 我觉得这道题给我的启示如下
- 首先是我们需要考虑当前值需要从哪一些地方转移, 会影响什么东西
- 其次, 我们可以对于没有修改的, 然后需要查询的值去进行预处理或者说对于每一个位置去处理好, 这样可以把复杂度优化很多
- 对于把上一个元素给更改啊这样会对前面的奉献所产生的影响, 需要考虑进入方程当中
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N], lst[N];
long long s[N];
long long f[N];
void solve() {
memset(f, 0, sizeof(f));
memset(lst, 0, sizeof(lst));
memset(s, 0, sizeof(s));
int n;
cin >> n;
for(int i = 1;i <= n;i++) {
cin >> a[i];
}
for(int i = 2;i <= n;i++) {
s[i] = ((a[i] == a[i - 1]) ? s[i - 1] + a[i] : s[i - 1]);
}
for(int i = 1;i <= n;i++) {
f[i] = f[i - 1];
if(lst[a[i]]) {
f[i] = max({f[i], f[lst[a[i]] + 1] + a[i] + s[i] - s[lst[a[i]] + 1]});
}
lst[a[i]] = i;
}
cout << f[n] << '\n';
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
int T;
cin >> T;
while(T--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号