Codeforces Round 932 (Div. 2)

写在前面

比赛地址:https://codeforces.com/contest/1935

被精心构造的 C 的样例鲨了的一集。

妈的天使骚骚☆REBOOT完全就是他妈拔作啊我草,要是被人知道我他妈推了全线要被笑话一辈子吧、、、

A

签到。

操作偶数次,则答案仅可能为 sreverse(s) + s,更长的字符串的前缀一定为二者之一,则选择这两者是最优的。

则仅需比较 sreverse(s) 的字典序即可。

复制复制
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n; std::cin >> n;
std::string s; std::cin >> s;
std::string t = s; std::reverse(t.begin(), t.end());
if (s <= t) std::cout << s << "\n";
else std::cout << t + s << "\n";
}
return 0;
}

B

枚举。

显然答案仅可能为 mexi=1nai 或无解。

于是检查 mexi=1nai 是否可以成为答案。发现若可以分成多段,则一定可以分成两段,于是仅需枚举两段互补的前缀后缀并检查它们的 mex 是否为 mexi=1nai 即可。

赛时写的比较傻比,先找到了最短的满足 mex=mexi=1nai 的前缀,然后再检查剩下的后缀是否也满足条件。

//枚举
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, ans, a[kN];
bool yes[kN];
std::vector <std::pair <int, int> > sol;
int cntnum, nowtime, cnt[kN], tim[kN];
//=============================================================
void add(int x_) {
if (tim[x_] != nowtime) cnt[x_] = 0, tim[x_] = nowtime;
if (!cnt[x_]) ++ cntnum;
++ cnt[x_];
}
void clear() {
cntnum = 0;
++ nowtime;
}
void check(int mex_) {
int sum = 0, last = 1;
sol.clear();
for (int i = 1; i <= n; ++ i) {
if (a[i] < mex_) add(a[i]);
if (cntnum == mex_) {
++ sum;
clear();
if (sol.empty()) sol.push_back(std::make_pair(last, i)), last = i + 1;
}
}
sol.push_back(std::make_pair(last, n));
if (sum >= 2) ans = mex_;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
ans = -1;
sol.clear();
clear();
std::cin >> n;
for (int i = 0; i <= n; ++ i) yes[i] = 0;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
yes[a[i]] = 1;
}
for (int i = 0; i <= n; ++ i) {
if (!yes[i]) {
check(i);
break;
}
}
if (ans == -1) {
std::cout << -1 << "\n";
continue;
}
std::cout << sol.size() << "\n";
for (auto x: sol)
std::cout << x.first << " " << x.second << "\n";
}
return 0;
}

C

枚举,排序,线段树

被精心构造的 C 的样例鲨了的一集。

这个贡献的形式显然是典中典,考虑将所有元素按 b 排序然后枚举区间 [l,r] 表示钦定选择了元素 l,r,使选择的元素满足后半部分贡献为 brbl,之后应在 (l,r) 中再选择尽可能多的元素使总贡献不超过 T,此时仅需考虑 a 的贡献即可。

显然应当按照 a 升序选择元素,一个显然的想法是用线段树维护 (l,r) 中元素的属性 a,以 a 的值为下标维护区间元素个数与区间贡献之和,然后在线段树上以上限 T(brbl)(ar+al) 二分即可得到至多能选择元素的数量。

在枚举区间的同时维护线段树即可。注意对 a 先离散化,总时间复杂度 O(n2logn) 级别。

为什么想到这个?因为前天刚写了一个线段树上二分的题,直接复制过来用了哈哈

O(n2) 的 DP 解法。

赛时线段树:

//枚举,排序,线段树
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 2010;
//=============================================================
int n, datanum, b[kN];
pii a[kN];
LL ans, l;
//=============================================================
namespace Seg {
#define ls (now_<<1)
#define rs (now_<<1|1)
#define mid ((L_+R_)>>1)
const int kNode = kN << 2;
LL sum[kNode], cnt[kNode];
void Pushup(int now_) {
sum[now_] = sum[ls] + sum[rs];
cnt[now_] = cnt[ls] + cnt[rs];
}
void Insert(int now_, int L_, int R_, int pos_, LL sum_, LL cnt_) {
if (L_ == R_) {
sum[now_] += sum_;
cnt[now_] += cnt_;
return ;
}
if (pos_ <= mid) Insert(ls, L_, mid, pos_, sum_, cnt_);
else Insert(rs, mid + 1, R_, pos_, sum_, cnt_);
Pushup(now_);
}
LL Query(int now_, int L_, int R_, LL lim_) {
if (L_ == R_) {
return std::min(cnt[now_], lim_ / b[L_]);
}
LL sl = sum[ls], cl = cnt[ls];
if (lim_ <= sl) return Query(ls, L_, mid, lim_);
return cl + Query(rs, mid + 1, R_, lim_ - sl);
}
#undef ls
#undef rs
#undef mid
}
void Solve() {
ans = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = i; j <= n; ++ j) {
LL cost = 1ll * b[a[i].second] + 1ll * (j > i) * b[a[j].second] + a[j].first - a[i].first;
if (l >= cost) {
LL c = 1 + (j > i) + Seg::Query(1, 1, datanum, l - cost);
ans = std::max(ans, c);
}
if (j > i) Seg::Insert(1, 1, datanum, a[j].second, b[a[j].second], 1);
}
for (int j = i; j <= n; ++ j) {
if (j > i) Seg::Insert(1, 1, datanum, a[j].second, -b[a[j].second], -1);
}
}
}
void Init() {
ans = 0;
std::cin >> n >> l;
for (int i = 1; i <= n; ++ i) {
int x, y; std::cin >> x >> y;
a[i] = mp(y, x);
}
std::sort(a + 1, a + n + 1);
for (int i = 1; i <= n; ++ i) b[i] = a[i].second;
std::sort(b + 1, b + n + 1);
datanum = std::unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; ++ i) {
a[i].second = std::lower_bound(b + 1, b + datanum + 1, a[i].second) - b;
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
Init();
Solve();
std::cout << ans << "\n";
}
return 0;
}
/*
1
5 100
1 1
1000 2
1000 3
1000 4
1 98
*/

D

数学,容斥

一眼题。C 实在调不出来看了一眼就会的呃呃题,五分钟过了之后继续调 C 呃呃

考虑求补集,即求多少二元组 (x,y)(xy) 满足 (x+y{si})(yx{si})。保证了 si 两两不同,则简单容斥下即求下列三部分二元组的数量:

  • f1:(x+y{si}),数量为 isi2+1
  • f2:(yx{si}),数量为 icsi+1
  • f3:(x+y{si})(yx{sj}),若二元组满足该条件,则有 ijsisj(mod2),则记 simod2=0/1 的数量分别为 k0,k1,二元组数量为 k0(k0+1)2+k1(k1+1)2

答案即为 c(c+1)2f0f1+f2,复杂度 O(n) 级别。

//数学,容斥
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 3e5 + 10;
//=============================================================
int n, c, s[kN];
LL ans;
//=============================================================
void Solve() {
ans = 1ll * (c + 1) * (c + 2) / 2ll;
int cnt[2] = {0, 0};
for (int i = 1; i <= n; ++ i) {
ans -= s[i] / 2ll + 1;
ans -= c - s[i] + 1;
++ cnt[s[i] % 2];
}
ans += 1ll * cnt[0] * (cnt[0] + 1ll) / 2ll;
ans += 1ll * cnt[1] * (cnt[1] + 1ll) / 2ll;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> c;
for (int i = 1; i <= n; ++ i) std::cin >> s[i];
Solve();
std::cout << ans << "\n";
}
return 0;
}

E

位运算,贪心,线段树

好玩的按位或。

先考虑 1in, xi=0 的情况,考虑按位贪心。设当前降序枚举到 i(0i29) 位,记 ci 表示 [l,r] 内有多少元素的 y 此位为 1:

  • ci=0:则此位无贡献。
  • ci=1:贡献为 2i,来自唯一的此位为 1 的数。
  • ci2:贡献为 2i+(2i1),令 y 此位为 1 的两个元素一个取 y,一个取 (y2i)or(2i1) 即可,则之后所有位均有贡献,直接停止枚举即可。

x0 时类似,同样考虑按位贪心并考虑每位为 1 的有多少个数。ci=0/1 的情况不受影响,然而对于 ci 可能有 2i1<x,使对应元素无法取到 2i1 影响 ci2 的贡献。但想法还是相同的:对于 ci2 的位,仅取其中一个保留第 i 位贡献,其他数向下调整使得某位 j 之后的所有位均有贡献。

手玩下发现满足条件的最大的 j 即为 x,y 中按照降序第一位不同的,由于 y>x 则一定有 yj 位为 1,xj 位为 0,则此时该元素可取到 (y2j)or(2j1)x,使 j 位之后的贡献均为 1——感觉和特殊情况很类似?

于是考虑先预处理出每个元素的 x,y 中按照降序第一位不同的位 j,发现每个元素 j 位之前的部分一定可以贡献到答案中,于是先将这部分提取出来线段树维护区间按位或;对于 j 位及之后的部分 yand(2j+11),此时相当于 x=0,y=yand(2j+11) 的对 x 无限制的情况,可以套用一开始的特殊情况,前缀和维护区间内有多少元素某位为 1 即可。

注意特判 x=y 的情况,贡献恒为 x 直接用线段树维护即可。

总时间复杂度 O((n+m)(logn+logv)) 级别。

另外注意:取出 v 的第 i 位时尽量使用 v >> i & 1 而非 1 << i & v,后者不知道为什么会挂掉。

//位运算,贪心,线段树
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m, must[kN], cnt[31][kN];
//=============================================================
namespace Seg {
#define ls (now_<<1)
#define rs (now_<<1|1)
#define mid ((L_+R_)>>1)
const int kNode = kN << 2;
int t[kNode];
void Pushup(int now_) {
t[now_] = t[ls] | t[rs];
}
void Build(int now_, int L_, int R_) {
if (L_ == R_) {
t[now_] = must[L_];
return ;
}
Build(ls, L_, mid), Build(rs, mid + 1, R_);
Pushup(now_);
}
int Query(int now_, int L_, int R_, int l_, int r_) {
if (l_ <= L_ && R_ <= r_) return t[now_];
int ret = 0;
if (l_ <= mid) ret |= Query(ls, L_, mid, l_, r_);
if (r_ > mid) ret |= Query(rs, mid + 1, R_, l_, r_);
return ret;
}
#undef ls
#undef rs
#undef mid
}
void Init() {
std::cin >> n;
for (int i = 1; i <= n; ++ i) {
int x, y; std::cin >> x >> y;
for (int j = 29; j >= 0; -- j) {
cnt[j][i] = cnt[j][i - 1];
}
if (x == y) {
must[i] = x;
} else {
int val = (1 << ((int) log2(x ^ y) + 1)) - 1;
must[i] = y - (y & val);
val = y & val;
for (int j = 29; j >= 0; -- j) {
cnt[j][i] += ((val >> j & 1) > 0);
}
}
}
Seg::Build(1, 1, n);
}
int Query(int l_, int r_) {
int ans = Seg::Query(1, 1, n, l_, r_);
for (int i = 29; i >= 0; -- i) {
int c = cnt[i][r_] - cnt[i][l_ - 1] + (ans >> i & 1);
if (c > 1) {
ans |= (1 << (i + 1)) - 1;
} else if (c == 1) {
ans |= (1 << i);
}
}
return ans;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
Init();
std::cin >> m;
while (m --) {
int l_, r_; std::cin >> l_ >> r_;
std::cout << Query(l_, r_) << " ";
}
std::cout << "\n";
}
return 0;
}
/*
1
1
2 2
1
1 1
*/

写在最后

学到了什么:

  • B:区间 mex 全局 mex
  • E:提取贡献,转化为特殊情况。取出 v 的第 i 位时尽量使用 v >> i & 1 而非 1 << i & v,后者不知道为什么会挂掉。
posted @   Luckyblock  阅读(38)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示