Codeforces Round 968 (Div. 2)
良心出题人给了中文题解!!!
A. Turtle and Good Strings
长度为 的字符串至少分成两段,使 ,第 段的首字符不等于第 段的尾字符
第一个字符一定作为首字符,最后一个字符一定作为尾字符,只要判断这两个字符是否相等即可
相等的话一定无解,不相等一定有解
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
string s;
int main()
{
int T = read();
while(T--)
{
int len = read();
cin >> s;
if(s[0] == s[len - 1]) printf("NO\n");
else printf("YES\n");
}
return 0;
}
B. Turtle and Piggy Are Playing a Game 2
假设最终答案为 , 只需要把小于 的值删除, 只需要把大于 的值删除
等价于对于一个序列,第一步删除最小值,第二步删除最大值,重复操作直至只剩一个数,答案即为第 小的数
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 1e5 + 5;
int a[N];
int main()
{
int T = read();
while(T--)
{
int n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
sort(a + 1, a + n + 1);
printf("%d\n", a[(n >> 1) + 1]);
}
return 0;
}
C. Turtle and Good Pairs
考场上手动模拟 不做贡献的条件,胡乱推导后得出结论:尽可能使相邻字符不相同
于是用 维护出现次数最多的字符以及它的个数,每次取出次数最多的字符填在第 个位置,并将次数-1
为了使相邻字符不同,先不将第 个字符放回集合(除非下一步没得取了,被迫相邻字符相同),先取出现次数次大的字符
有种摩尔投票统计绝对众数的感觉,当不存在绝对众数时不会出现相邻字符相同的情况
然而猜结论是不行的,需要严谨的数学证明:
题解中最小化 即为尽可能使相邻字符不同
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 5;
int cnt[N];
multiset< pair<int, int> > S;
multiset< pair<int, int> > SS;
string s;
int main()
{
int T = read();
while(T--)
{
int len = read();
cin >> s;
for(int i = 0; i <= 25; ++i) cnt[i] = 0;
for(int i = 0; i < len; ++i) cnt[s[i] - 'a']++;
S.clear(), SS.clear();
for(int i = 0; i <= 25; ++i)
if(cnt[i]) S.emplace(pair<int, int>(cnt[i], i));
for(int i = 1; i <= len; ++i)
{
pair<int, int> now = *prev(S.end());
S.erase(now);
now.first --;
printf("%c", (char)(now.second + 'a'));
if(!SS.empty())
{
S.emplace(*SS.begin());
SS.clear();
}
if(S.empty()) S.emplace(now);
else if(now.first ) SS.emplace(now);
}
printf("\n");
}
return 0;
}
D1. Turtle and a MEX Problem (Easy Version)
观察到可以使用 次操作同一个序列得到这个序列的第二 值
记第 个序列的 为 ,第二 为
则
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 5;
vector<int> a[N];
int mex[N], mmex[N];
int cnt[N];
void solve(int id)
{
int len = a[id].size();
for(int i = 0; i <= len + 1; ++i) cnt[i] = 0;
for(int i = 0; i < len; ++i)
{
int x = a[id][i];
if(x <= len) cnt[x]++;
}
mex[id] = 0;
for(int i = 0; i <= len + 1; ++i)
{
if(cnt[i] == 0)
{
mex[id] = i;
break;
}
}
mmex[id] = 0;
for(int i = mex[id] + 1; i <= len + 1; ++i)
{
if(cnt[i] == 0)
{
mmex[id] = i;
break;
}
}
}
ll add(int l, int r)
{
if(l > r) return 0;
return 1ll * (l + r) * (r - l + 1) / 2;
}
int main()
{
int T = read();
while(T--)
{
int n = read(), m = read();
for(int i = 1; i <= n; ++i)
{
a[i].clear();
int len = read();
while(len--)
{
int x = read();
a[i].emplace_back(x);
}
solve(i);
}
sort(mmex + 1, mmex + n + 1);
ll ans = 0, mx = mmex[n];
ans = mx * min((mx + 1), 1ll * (m + 1)) + add(mx + 1, m);
printf("%lld\n", ans);
}
return 0;
}
D2. Turtle and a MEX Problem (Hard Version)
增加了限制:同一个序列 至多操作 次
考虑有向图:由 向 连边
一次操作相当于将 变为 ,并断开 的一条出边
当 有超过 条出边时,断开哪条边都可以,也就是哪条边都可以走
设 表示当前位于点 ,每次选择一条出边能够到达的点编号的最大值
在有向图上倒序
如何统计答案?
都可以取到
都可以取到
当 的出边个数大于 时, 都可以取到
记 ,小于等于 的枚举,大于 的 , 最优
总结
- 情况复杂时先将所有想到的情况列举下来
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 5;
vector<int> a[N];
int mex[N], mmex[N]; // mmex最大不超过len + 1
int cnt[N];
int dp[N], in[N], out[N];
vector<int> e[N];
queue<int> q;
void solve(int id)
{
int len = a[id].size();
for(int i = 0; i <= len + 1; ++i) cnt[i] = 0;
for(int i = 0; i < len; ++i)
{
int x = a[id][i];
if(x <= len) cnt[x]++;
}
mex[id] = 0;
for(int i = 0; i <= len + 1; ++i)
{
if(cnt[i] == 0)
{
mex[id] = i;
break;
}
}
mmex[id] = 0;
for(int i = mex[id] + 1; i <= len + 1; ++i)
{
if(cnt[i] == 0)
{
mmex[id] = i;
break;
}
}
}
ll add(ll l, ll r)
{
if(l > r) return 0;
return (l + r) * (r - l + 1) / 2;
}
int main()
{
int T = read();
while(T--)
{
int n = read(), m = read(), mxlen = 0;
for(int i = 1; i <= n; ++i)
{
a[i].clear();
int len = read();
mxlen = max(mxlen, len);
while(len--)
{
int x = read();
a[i].emplace_back(x);
}
solve(i);
}
mxlen++;
for(int i = 0; i <= mxlen; ++i)
{
dp[i] = 0, in[i] = 0, out[i] = 0;
e[i].clear();
}
while(!q.empty()) q.pop();
for(int i = 1; i <= n; ++i) e[mmex[i]].push_back(mex[i]), ++in[mex[i]], ++out[mex[i]];
for(int i = 0; i <= mxlen; ++i)
if(in[i] == 0) q.emplace(i);
while(!q.empty())
{
int id = q.front();
q.pop();
dp[id] = max(dp[id], id);
for(int v : e[id])
{
dp[v] = max(dp[v], dp[id]);
--in[v];
if(in[v] == 0) q.emplace(v);
}
}
int mx = 0;
ll ans = 0;
for(int i = 1; i <= n; ++i)
{
mx = max(mx, mex[i]);
if(out[mex[i]] >= 2) mx = max(mx, dp[mex[i]]);
}
ans = add(mx + 1, m);
for(int i = 0; i <= min(mx, m); ++i) ans += max(mx, dp[i]);
printf("%lld\n", ans);
}
return 0;
}
E1. Turtle and Inversions (Easy Version)
使所有区间的前缀 的 小于所有区间的后缀 的
将序列的数分为两种,小数和大数,小数为 ,大数为 ,将排列转化为 序列
一个排列是有趣排列的充要条件为,它的某个 序列为有趣序列,即对于任意一个区间 所有的 在 前面,且 和 至少都有一个
对于一个有趣序列( 和 的位置已经固定),为了使逆序对数最多,可以贪心地将大数 从大到小排列,小数 从大到小排列
一种做法呼之欲出:枚举 的个数,然后将大数小数都从大到小排列,统计逆序对,
题解给了一种 的 :
考虑 做法:
我们将必须填 的位置称为固定 ,必须填 的位置成为固定 ,其他位置称为自由点
设 表示位置 之前有几个固定 ,设 表示位置 之前有几个自由点
假设目前枚举大数 的个数为 个,其中 个为自由 ,将它们全部放在最靠前的自由点上最优
当 为固定 时,能和它产生逆序对的 的个数为
当 为固定 时,能和它产生逆序对的 的个数为
当 为自由点时,不论值为 或者 ,能和它产生逆序对的 的个数为
此时还缺少所有的 相互之间的贡献,即为
观察 , 不随 变化为定值,单独记录
随 单调不降,将 排序后,考虑位置 ,对于 , ,取 ,对于 , 取
对于任意 可以 解决
总结
- 将排列大小关系转化为 序列
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 5e3 + 5;
int l[N], r[N], c[N], C[N];
int a[N], b[N];
void solve()
{
int n = read(), m = read();
for(int i = 1; i <= n; ++i) c[i] = -1, C[i] = 0;
for(int i = 1; i <= m; ++i)
{
l[i] = read(), r[i] = read();
c[l[i]] = 0, c[r[i]] = 1;
}
int L = m, R = n - m;
ll tmp = 0;
for(int i = 1; i <= n; ++i)
{
if(c[i] == -1) C[i] = 1, c[i] = 0;
a[i] = C[i - 1], tmp += c[i - 1];
C[i] += C[i - 1], c[i] += c[i - 1]; // 第i个数前面有多少空位,有多少个必选的1
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; ++i) b[i] = b[i - 1] + a[i];
ll ans = 0, now = 0;
int pos = 0;
for(int t = L; t <= R; ++t) // t个1
{
now = 1ll * (n - t) * (n - t - 1) / 2;
while(pos < n && a[pos + 1] <= t - m) ++pos;
ans = max(ans, now + b[pos] + 1ll * (n - pos) * (t - m));
}
printf("%lld\n", ans + tmp);
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
E2. Turtle and Inversions (Hard Version)
增加了限制:区间可以相交
考虑两个区间
对于 一定都为 ,对于 或 一定都为
剩余区间为 ,即完成了一次区间合并
若干次区间合并后改为区间不交,解法同
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 5e3 + 5;
int c[N], C[N];
int a[N], b[N];
pair<int, int> jian[N];
bool cmp(pair<int, int> a, pair<int, int> b)
{
if(a.first == b.first ) return a.second > b.second ;
return a.first < b.first ;
}
void solve()
{
int n = read(), m = read(), flag = 0;
for(int i = 1; i <= n; ++i) c[i] = -1, C[i] = 0;
for(int i = 1; i <= m; ++i)
{
jian[i].first = read(), jian[i].second = read();
if(!flag && c[jian[i].first ] == 1) flag = 1;
if(!flag && c[jian[i].second ] == 0) flag = 1;
c[jian[i].first ] = 0, c[jian[i].second ] = 1;
}
sort(jian + 1, jian + m + 1, cmp);
m = unique(jian + 1, jian + m + 1) - (jian + 1);
for(int i = 2; i <= m; ++i)
{
if(jian[i].first > jian[i - 1].second ) continue;
for(int j = jian[i - 1].first + 1; j < jian[i].first ; ++j)
{
if(c[j] == 1) flag = 1;
else c[j] = 0;
}
for(int j = min(jian[i - 1].second , jian[i].second ) + 1; j < max(jian[i - 1].second , jian[i].second ); ++j)
{
if(c[j] == 0) flag = 1;
else c[j] = 1;
}
jian[i].second = jian[i - 1].second ;
}
if(flag){ printf("-1\n"); return ; }
int L = 0, R = 0;
for(int i = 1; i <= n; ++i)
{
if(c[i] == 1) ++L;
if(c[i] == 0) ++R;
}
R = n - R;
ll tmp = 0;
for(int i = 1; i <= n; ++i)
{
if(c[i] == -1) C[i] = 1, c[i] = 0;
a[i] = C[i - 1], tmp += c[i - 1];
C[i] += C[i - 1], c[i] += c[i - 1]; // 第i个数前面有多少空位,有多少个必选的1
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; ++i) b[i] = b[i - 1] + a[i];
ll ans = 0, now = 0;
int pos = 0;
for(int t = L; t <= R; ++t) // t个1
{
now = 1ll * (n - t) * (n - t - 1) / 2;
while(pos < n && a[pos + 1] <= t - L) ++pos;
ans = max(ans, now + b[pos] + 1ll * (n - pos) * (t - L));
}
printf("%lld\n", ans + tmp);
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
F. Turtle and Three Sequences
题解写的很好了,做题时可以先写朴素 ,再逐步优化
关于 :最终答案是 个数,映射值是随机的,因此是 种情况,只有恰好构成一个 1 ~ m 的排列才正确,即
总结
当要从 个元素中选出 个元素, 较大而 较小,且选取时元素之间相互限制,可以考虑用 + 状压
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
int read()
{
int x = 0, f = 0; char c = getchar();
while (c < '0' || c > '9') f = c == '-', c = getchar();
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int inf = 0x3f3f3f3f;
const int N = 3e3 + 5;
int n = read(), m = read();
int a[N], b[N], c[N];
int id[N];
struct BIT
{
int c[N];
BIT(){ for(int i = 0; i <= 3001; ++i) c[i] = -inf; }
int query(int x)
{
int ans = -inf;
while(x) ans = max(ans, c[x]), x ^= (x & -x);
return ans;
}
void update(int x, int val)
{ while(x <= n + 1) c[x] = max(c[x], val), x += (x & -x); }
void clear(){ for(int i = 0; i <= 3001; ++i) c[i] = -inf; }
} dp[32];
int main()
{
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= n; ++i) b[i] = read();
for(int i = 1; i <= n; ++i) c[i] = read();
int T = 300, ans = -inf;
while(T--)
{
for(int i = 1; i <= n; ++i) id[i] = rnd() % m;
for(int i = 0; i < (1 << m); ++i) dp[i].clear();
dp[0].update(1, 0);
for(int i = 1; i <= n; ++i)
{
for(int S = (1 << m) - 1; ~S; --S)
{
if(!(S & (1 << id[b[i]]))) continue;
int u = dp[S].query(a[i] + 1), v = dp[S ^ (1 << id[b[i]])].query(a[i] + 1);
if(u < v + c[i]) dp[S].update(a[i] + 1, v + c[i]);
}
}
ans = max(ans, dp[(1 << m) - 1].query(n + 1));
}
if(ans < m) printf("-1\n");
else printf("%d\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】