## 和除或
#容斥 #组合数学
首先,我们通过观察可以猜测 $f(X,Y)=\left\lceil{\cfrac{X+Y}{X|Y}}\right\rceil$ 的值不是 $1$ 就是 $2$,很明显我们猜对了因为打表找不出反例,因此我们需要进行思考什么情况下取 $1$ 什么情况下取 $2$。
由于这里是向上取整,因此取 $1$ 情况只有 $X+Y=X\operatorname{|}Y$ 了,也就是 $X$ 和 $Y$ 的 $1$ 位没有相交的,即 $X\operatorname{\&}Y=0$。其余所有 $X\operatorname{\&}Y\ne 0$ 的场景,都是取的 $2$。原函数的值转化为 $1+[X\operatorname{\&}Y\ne 0]$。
$
\begin{aligned}
\sum_{1\le i<j\le n}f(a_i,x_j)&=\sum_{1\le i<j\le n}1+\sum_{1\le i<j\le n}[X\operatorname{\&}Y\ne 0] \\
&=\cfrac{n(n-1)}{2}+\sum_{1\le i<j\le n}[X\operatorname{\&}Y\ne 0]
\end{aligned}
$
后面的不好计算,因此使用容斥原理。令 $P_k$ 表示第 $k$ 位都为 $1$ 的集合,我们要求的是 $|\cup_k P_k|$,根据容斥原理
$
|\cup_k P_k|=\sum|P_k|-\sum|P_k\cap P_l|+\sum|P_k\cap P_l\cap P_m|
$
令 $g(x)$ 表示满足 $a_i\operatorname{\&}x=x$ 的 $i$ 的数量,那么所有满足 $[a_i\operatorname{\&}a_j\ne 0]$ 的数对的数量为
$
\sum_{x\ne 0}(-1)^{\text{popcount(x)}-1}\times \cfrac{g(x)\times g(x)-1)}{2}
$
因此我们只需要预先计算出 $g(x)$(即多少个 $a_i$ 的子集是 $x$ 的数量)就行了。
```cpp
#include <iostream>
#include <map>
using namespace std;
using LL = long long;
const LL kMaxN = 2e5 + 5;
LL a[kMaxN], t, n, ans;
map<LL, LL> f, dp;
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n;
f.clear(), dp.clear();
for (LL i = 1; i <= n; i++) {
cin >> a[i];
f[a[i]]++;
}
ans = n * (n - 1) / 2;
for (auto &i : f) {
for (LL j = i.first; j; j = (j - 1) & i.first) {
dp[j] += i.second;
}
dp[0] += i.second;
}
for (auto &i : dp) {
if (!i.first) {
continue;
}
if (__builtin_popcount(i.first) % 2) {
ans += i.second * (i.second - 1) / 2;
} else {
ans -= i.second * (i.second - 1) / 2;
}
}
cout << ans << '\n';
}
return 0;
}
```
## 礼物
#贪心
既然是不能有连续的长度为 $k$ 的段,那么我就找到第一个连续的长度为 $k$ 的段的位置 $l$,然后往右枚举 $r$,判断一下替换 $[l,r]$ 能否满足条件即可。
至于判断是否满足条件还是有一点复杂的,过程如下:
- $s_l\ne s_r$,否则 $s_r$ 反转过去仍然能够组成长度为 $k$ 的连续段;
- 反转的区间 $[l,r]$ 内部不能有长度为 $k$ 的连续段,因为反转操作不会破坏反转区间内的连续段;
- 如果 $s_l=s_{r+1}$ 的话,那么 $l$ 往后的连续段的长度加上 $r+1$ 往后的连续段的长度要小于 $k$,因为 $l$ 反转过去后会和 $r+1$ 拼起来。
如果以上条件都满足,那么就是有解的。
欸,但我们提交上去就会发现大样例 wa 了,被 `7 4 AABBBBB` 卡了,因为我们总是往后反转而不是往前反转,但是重新写一遍又太复杂了。
于是我就正着跑一遍贪心,反着跑一遍贪心,这样子就可以通过所有数据了,欢迎来 Hack。
```cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int kMaxN = 2e5 + 5;
int suf[kMaxN], len[kMaxN], t, n, k;
char s[kMaxN];
bool check() {
int cnt = 1;
suf[n + 1] = 1;
len[n + 1] = 0;
for (int i = n; i >= 1; i--) {
if (s[i] == s[i + 1]) {
cnt++;
} else {
cnt = 1;
}
suf[i] = suf[i + 1] && cnt < k;
len[i] = cnt; // i 往后的相同段的长度
}
if (suf[1]) {
return 1;
}
cnt = 1;
bool ans = 0;
s[n + 1] = 'C';
for (int i = 2; i <= n; i++) {
if (s[i] == s[i - 1]) {
cnt++;
} else {
cnt = 1;
}
if (cnt >= k) {
// 反转 [i, j]
cnt = 1;
int pre = 1;
bool lead = 1;
for (int j = i + 1; j <= n; j++) {
if (s[j] == s[j - 1]) {
cnt++;
} else {
cnt = 1;
}
if (lead && s[j] == s[i]) {
pre++;
} else {
lead = 0;
}
if (cnt >= k) {
break;
}
if (s[j] == s[i]) {
continue;
}
if (suf[j + 1] && (s[i] != s[j + 1] || pre + len[j + 1] < k)) {
return 1;
}
}
break;
}
}
return 0;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n >> k >> (s + 1);
bool ans = check();
reverse(s + 1, s + n + 1);
ans |= check();
cout << (ans ? "YES" : "NO") << '\n';
}
return 0;
}
```
## 连续段的价值
#二分 #状压dp
首先答案肯定是有单调性的,如果 $ans$ 是答案的话 $ans-1$ 也可以满足,因此我们可以考虑使用二分答案。注意到 $k\le 17$,这很有状压的嫌疑。
接下来我们考虑如何对我们二分的值进行判断。假设当前二分的答案为 $x$,那么我们设 $dp_{i,j}$ 表示为当前已经满足条件的字符集位为 $i$,字符串的长度能否为 $j$,转移就是枚举一个新的字符在 $i$ 里面没出现的,然后往后遍历原始字符串找到一个长度为 $x$ 的连续段进行转移,判断函数时间复杂度 $\mathcal O(2^k k n^2)$,非常恐怖。
考虑预处理转移部分,令 $e_{i,j}$ 表示为 $i$ 字符在第 $j$ 个字符开始,往后至少要到 $e_{i,j}$,在 $[j,e_{i,j}]$ 这个段里面有长度为 $x$ 的连续 $i$ 字符段。$e$ 可以 $\mathcal O(kn)$ 预处理出来,转移就变成 $\mathcal O(1)$ 的了,判断函数时间复杂度将为 $\mathcal O(2^k kn)$,可以通过 $75$ 分。
接下来优化 dp 状态。其实我们没必要存储状态 $j$,因为对于所有 $dp_{i,j}$ 值为 $1$ 的当中,$j$ 越小的状态是越优的,而且经过相同字符的转移时不会比 $j$ 更大的状态更劣,因此我们将 $j$ 拆掉作为 $dp_i$ 的一个最优化属性。
设 $dp_i$ 表示为当前已经选择的字符集为 $i$,至少需要 $dp_i$ 长度的前缀。转移时枚举字符 $j$,由 $dp_i\rightarrow dp_{i\operatorname{|}j}+e_{j,dp_i}$。判断函数时间复杂度 $\mathcal O(kn+2^k k)$,总时间复杂度 $\mathcal O\left(\left(n+2^k\right)k\log_2\left(\cfrac{n}{k}\right)\right)$。
```cpp
#include <iostream>
using namespace std;
const int kMaxN = 2e5 + 5, kMaxK = 18;
int e[kMaxK][kMaxN], dp[1 << kMaxK], n, k, ans;
char s[kMaxN];
bool check(int x) {
for (int i = 0; i < k; i++) {
e[i][n + 1] = n + 1;
for (int j = n, cnt = 0; j >= 1; j--) {
if (s[j] == '?' || s[j] == 'a' + i) {
cnt++;
} else {
cnt = 0;
}
if (cnt >= x) {
e[i][j] = j + x - 1;
} else {
e[i][j] = e[i][j + 1];
}
}
}
fill(dp, dp + (1 << kMaxK), 1e9);
dp[0] = 0;
for (int i = 0; i < (1 << k); i++) {
for (int j = 0; j < k; j++) {
if (!(i & (1 << j)) && dp[i] < n) {
dp[i | (1 << j)] = min(dp[i | (1 << j)], e[j][dp[i] + 1]);
}
}
}
return dp[(1 << k) - 1] <= n;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> k >> (s + 1);
for (int l = 0, r = n / k; l <= r; ) {
int m = (l + r) / 2;
if (check(m)) {
ans = m;
l = m + 1;
} else {
r = m - 1;
}
}
cout << ans << '\n';
return 0;
}
```
## 送外卖
#线段树 #树状数组
本题我写的是暴力。
- 对于子任务 1,模拟蜗蜗的送餐操作。最初时间戳 $tm=x_l$,每次往后送餐 $tm\gets tm+t_i$,但是如果时间太早了就要等 $tm\gets \max(tm, x_{i+1})$,如果 $tm>y_{i+1}$ 就是超时了。
- 对于子任务 3,用树状数组维护 $t_i$ 的区间和,如果区间内 $t_i$ 的和超过了任意 $y-x$,那么就是超时了。
```cpp
#include <iostream>
using namespace std;
using LL = long long;
const LL kMaxN = 1e6 + 5;
LL l[kMaxN], r[kMaxN], a[kMaxN], tr[kMaxN], t, n, q;
bool same() {
for (int i = 2; i <= n; i++) {
if (l[i] != l[i - 1] || r[i] != r[i - 1]) {
return 0;
}
}
return 1;
}
void modify(int x, int y) {
for (; x <= n; x += x & -x) {
tr[x] += y;
}
}
int query(int x) {
int res = 0;
for (; x; x -= x & -x) {
res += tr[x];
}
return res;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n;
for (LL i = 1; i <= n; i++) {
cin >> l[i];
}
for (LL i = 1; i <= n; i++) {
cin >> r[i];
}
for (LL i = 1; i < n; i++) {
cin >> a[i];
}
cin >> q;
if (n <= 2000 && q <= 2000) {
for (LL o, p, x, y; q; q--) {
cin >> o;
if (o == 0) {
cin >> x >> y;
LL p = l[x], ans = 1;
for (LL i = x; i < y; i++) {
p = max(l[i + 1], p + a[i]);
if (p > r[i + 1]) {
ans = 0;
break;
}
}
cout << (ans ? "Yes" : "No") << '\n';
} else if (o == 1) {
cin >> x >> y;
a[x] = y;
} else if (o == 2) {
cin >> p >> x >> y;
l[p] = x;
r[p] = y;
}
}
} else if (same()) {
fill(tr + 1, tr + n + 1, 0);
for (LL i = 1; i <= n; i++) {
modify(i, a[i]);
}
for (LL o, p, x, y; q; q--) {
cin >> o;
if (o == 0) {
cin >> x >> y;
cout << (query(y - 1) - query(x - 1) <= r[y] - l[x] ? "Yes" : "No") << '\n';
} else if (o == 1) {
cin >> x >> y;
modify(x, -a[x]);
a[x] = y;
modify(x, a[x]);
} else if (o == 2) {
}
}
}
}
return 0;
}
```