和除或
首先,我们通过观察可以猜测 的值不是 就是 ,很明显我们猜对了因为打表找不出反例,因此我们需要进行思考什么情况下取 什么情况下取 。
由于这里是向上取整,因此取 情况只有 了,也就是 和 的 位没有相交的,即 。其余所有 的场景,都是取的 。原函数的值转化为 。
后面的不好计算,因此使用容斥原理。令 表示第 位都为 的集合,我们要求的是 ,根据容斥原理
令 表示满足 的 的数量,那么所有满足 的数对的数量为
因此我们只需要预先计算出 (即多少个 的子集是 的数量)就行了。
#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;
}礼物
既然是不能有连续的长度为 的段,那么我就找到第一个连续的长度为 的段的位置 ,然后往右枚举 ,判断一下替换 能否满足条件即可。
至于判断是否满足条件还是有一点复杂的,过程如下:
- ,否则 反转过去仍然能够组成长度为 的连续段;
- 反转的区间 内部不能有长度为 的连续段,因为反转操作不会破坏反转区间内的连续段;
- 如果 的话,那么 往后的连续段的长度加上 往后的连续段的长度要小于 ,因为 反转过去后会和 拼起来。
如果以上条件都满足,那么就是有解的。
欸,但我们提交上去就会发现大样例 wa 了,被 7 4 AABBBBB 卡了,因为我们总是往后反转而不是往前反转,但是重新写一遍又太复杂了。
于是我就正着跑一遍贪心,反着跑一遍贪心,这样子就可以通过所有数据了,欢迎来 Hack。
#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 状态。其实我们没必要存储状态 ,因为对于所有 值为 的当中, 越小的状态是越优的,而且经过相同字符的转移时不会比 更大的状态更劣,因此我们将 拆掉作为 的一个最优化属性。
设 表示为当前已经选择的字符集为 ,至少需要 长度的前缀。转移时枚举字符 ,由 。判断函数时间复杂度 ,总时间复杂度 。
#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,模拟蜗蜗的送餐操作。最初时间戳 ,每次往后送餐 ,但是如果时间太早了就要等 ,如果 就是超时了。
- 对于子任务 3,用树状数组维护 的区间和,如果区间内 的和超过了任意 ,那么就是超时了。
#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;
}