CF1313D Happy New Year
CF1313D Happy New Year
思路
一道不错的思维题。入手点在于观察到 \(k \le 8\),意味着每个点至多被打上 \(8\) 个标记,数量很少。因此对于“每个点打上了哪些标记”,我们只需要用一个 \(8\) 位二进制数就可以表示所有状态。考虑状压 \(dp\)。
先离散化诅咒涉及到的所有点,接着记 \(f_{i, S}\) 表示第 \(i\) 个点,状态为 \(S\), 前 \(i\) 个点最多有多少是快乐的。
枚举该点所有状态,接着标记的转移可以 \(O(1)\) 实现,原因是当前点要么是一段诅咒的起点,要么是终点,我们只需要讨论要不要在当前添加这个标记或者删除这个标记就可以了。转移部分的代码是填表法实现的。
具体转移如下:
我们需要给当前点 \(i\)(实际长度为 \(len\)),附上一个特定的编号 \(k\),由题 \(k \le 8\)。记当前考虑状态 \(S\)。
-
若 \(i\) 是起点
- 若 \(k \in S\),意味着 \(k\) 是新加入的编号,要从 \(i - 1\) 处没有这个编号的状态更新过来。\(S\) 有奇数个标记产生新贡献。
\[f_{i, S} = f_{i - 1, S / \{ k\}} + len \times \texttt{__builtin_parity(S)} \]- 若 \(k \notin S\),意味着 \(k\) 是已加入的编号,直接从 \(i - 1\) 继承过来即可。\(S\) 有奇数个标记产生新贡献。
\[f_{i, S} = f_{i - 1, S} + len \times \texttt{__builtin_parity(S)} \]
-
若 \(i\) 是终点
- 若 \(k \in S\),这是不合法的。
- 若 \(k \notin S\),意味着编号 \(k\) 之前可能有,也可能没有,要两种情况取较大。\(S\) 有奇数个标记产生新贡献。
\[f_{i, S} = \max(f_{i - 1, S}, f_{i - 1, S / \{ k\}}) + len \times \texttt{__builtin_parity(S)} \]
你或许有这样的疑惑:为什么是从 \(S / \{k\}\) 这样的位置转移过来?那就要首先明确编号的出现只是为了方便数数!一个诅咒可能涉及到很多点,但一个点最多被 \(8\) 个诅咒影响,进一步地,我们其实只关心每个点受到的诅咒数量,并不关系是具体哪些诅咒。
因此我们采用给诅咒赋编号的方法,值得一提的是不同点同一编号的诅咒完全可能不是同一个诅咒。所谓的 \(S / \{k\}\),其实仅仅表示少了一个诅咒,而非少了这个特定的诅咒,这已经可以达到我们统计数量的目标了。(再具体点讲,覆盖或者抠掉了之前哪个诅咒不重要,重要的是表示能数量变化就行)
时间复杂度 \(O(255m)\)。代码中把 \(f\) 的第一维压掉了,注意转移顺序!!!编号分配见代码。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l); i <= (r); ++ i)
#define G(i,r,l) for(int i(r); i >= (l); -- i)
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
vector<pair<int, int> > a;
int f[300], vis[18];
int n, m, kt;
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m >> kt;
F(i, 1, n){
int L, R;
cin >> L >> R;
a.push_back({L, i});
a.push_back({R + 1, -i});
} int cnt = a.size();
sort(a.begin(), a.end());
F(i, 1, 255) f[i] = -inf;
F(i, 0, cnt - 1){
int id = a[i].second, k;
int len = (i == cnt - 1) ? 0 : (a[i + 1].first - a[i].first);
if(id > 0){
F(i, 0, 7){
if(vis[i] == 0){
k = i;
vis[k] = id;
break;
}
}//distribute an id
G(i, 255, 0){
if((i >> k) & 1){
f[i] = f[i ^ (1 << k)] + len * __builtin_parity(i);
// da xiao
}
else{
f[i] = f[i] + len * __builtin_parity(i);
}
}
}
else{
F(i, 0, 7){
if(vis[i] == -id){
k = i;
vis[k] = 0;
break;
}
}
F(i, 0, 255){
if((i >> k) & 1){
f[i] = -inf;
}
else{
f[i] = max(f[i], f[i ^ (1 << k)]) + len * __builtin_parity(i);
//xiao da
}
}
}
}
cout << f[0] << '\n';
return fflush(0), 0;
}
总结
本题关键是要想通只关注每个点上诅咒数量就可以,不需要关心具体的诅咒。其他题解中提到了扫描线这一思想,个人感觉不是重点。

浙公网安备 33010602011771号