Educational Codeforces Round 169 (Rated for Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/2004
又是手速场,最后把 F 口了没时间写了。小号又能上紫了爽,要是手快点还能更爽。
置顶广告:中南大学 ACM 集训队绝赞招新中!
有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!
没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!
到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜
A
签到。
数据范围很小,懒得多想了于是直接枚举插入点的位置检查。
//
/*
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::vector<int> a(n + 1), b;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
if (i >= 2) b.push_back(a[i] - a[i - 1]);
}
// std::sort(b.begin(), b.end());
int fl = 0;
for (int i = -100; i <= 200; ++ i) {
int maxd = 0;
for (int j = 1; j <= n; ++ j) {
maxd = std::max(maxd, abs(i - a[j]));
}
int flag = 1;
for (auto d: b)
if (maxd >= d) flag = 0;
if (flag == 1) {
fl = 1;
break;
}
}
std::cout << (fl ? "YES\n" : "NO\n");
}
return 0;
}
B
结论,特判。
恶心特判题,我写了一坨。
特别注意端点重合的情况!
//
/*
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 l, r, L, R, ans; std::cin >> l >> r >> L >> R;
if (l > R || L > r) {
ans = 1;
} else if (l == L && r == R) {
ans = R - L;
} else if (l <= L && R <= r && (l == L || R == r)) {
ans = R - L + 1;
} else if (L <= l && r <= R && (l == L || R == r)) {
ans = r - l + 1;
} else if (l < L && R < r) {
ans = R - L + 2;
} else if (L < l && r < R) {
ans = r - l + 2;
} else if (l <= L) {
ans = r - L + 2;
} else {
ans = R - l + 2;
}
std::cout << ans << "\n";
}
return 0;
}
/*
1
1 2
2 3
*/
C
贪心,排序
先不考虑风灵月影宗师 Bob 企图开挂的行为,发现在一局游戏中两人一定是将 \(a\) 按照权值递减排序,并交替地选择此时的最大值。
然后考虑 Bob 的修改。显然他应当保证他修改后增加的权值一定全部被自己拿到,则应当保证修改后各个权值降序排序后相对位置不发生改变。显然此时他的最优操作是对于降序排序后的数列 \(a_1\sim a_n\),对于他可以取到的所有 \(i\bmod 2 = 0\) 的位置 \(a_i\),都尝试将每个 \(a_{i}\) 修改为 \(a_{i - 1}\)。于是直接模拟即可。
//
/*
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, k; std::cin >> n >> k;
LL ans = 0;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) std::cin >> a[i];
std::sort(a.begin(), a.end(), std::greater<int>());
for (int i = 0; i < n; ++ i) {
if (i % 2 == 0) ans += a[i];
else {
int delta = std::min(k, a[i - 1] - a[i]);
a[i] += delta, k -= delta;
ans -= a[i];
}
}
std::cout << ans << "\n";
}
return 0;
}
D
枚举,结论
发现在大多数情况下给定的询问 \(x, y\) 均可以直达,此时的代价即为 \(|x - y|\)。
无法直达的情况 \(x, y\) 所在城市的种类只有三种:BG
和 RY
,BR
和 GY
,BY
和 GR
。容易发现对于这三种情况,仅需找到任意一个类型与它们均不同的城市 \(p\),就可以作为中转点使得 \(x, y\) 可互相到达,代价即为 \(|x - p| + |y - p|\)。
若中转城市恰好位于 \([x, y]\) 中则代价即为 \(|x - y|\),否则仅需检查距离 \(x, y\) 最近的中转城市即可。于是考虑按照位置升序维护每种城市的位置,对于每次询问先判断 \(x, y\) 是否可以直达,若无法直达则枚举中转城市的类型,检查 \([x + 1, y - 1]\) 中有无中转城市,若没有则在 \([1, x - 1], [y + 1, n]\) 中二分找到最优的中转城市即可。
总时间复杂度 \(O(n + q\log n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const int kInf = 1e9;
//=============================================================
int n, q, t[kN], cnt[7][kN];
std::map<std::string, int> type;
std::vector<int> pos[7];
//=============================================================
int oppo(int x_) {
return (x_ <= 3 ? x_ + 3 : x_ - 3);
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
//BG, BR, BY, GR, GY, or RY
type["BG"] = 1;
type["BR"] = 2;
type["BY"] = 3;
type["RY"] = 4;
type["GY"] = 5;
type["GR"] = 6;
int T; std::cin >> T;
while (T --) {
std::cin >> n >> q;
for (int i = 1; i <= 6; ++ i) pos[i].clear();
for (int i = 1; i <= n; ++ i) {
std::string s; std::cin >> s;
t[i] = type[s];
pos[t[i]].push_back(i);
for (int j = 1; j <= 6; ++ j) cnt[j][i] = cnt[j][i - 1];
cnt[t[i]][i] ++;
}
while (q --) {
int x, y; std::cin >> x >> y;
if (x > y) std::swap(x, y);
if (oppo(t[x]) != t[y]) {
std::cout << abs(x - y) << "\n";
continue;
}
int ans = kInf;
for (int i = 1; i <= 6; ++ i) {
if (i == t[x] || i == t[y]) continue;
if (cnt[i][y - 1] - cnt[i][x] != 0) ans = y - x;
if (cnt[i][x - 1]) {
int p = std::lower_bound(pos[i].begin(), pos[i].end(), x) - pos[i].begin();
-- p;
ans = std::min(ans, x - pos[i][p] + y - pos[i][p]);
}
if (cnt[i][n] - cnt[i][y]) {
int p = std::lower_bound(pos[i].begin(), pos[i].end(), y) - pos[i].begin();
ans = std::min(ans, pos[i][p] - y + pos[i][p] - x);
}
}
if (ans >= kInf) ans = -1;
std::cout << ans << "\n";
}
}
return 0;
}
E
博弈论,SG 函数
比较裸的 SG 函数问题,妈的标题不是 Not a Nim Problem 吗。
手玩下发现,对于单堆石子当且仅当个数 \(x\) 为奇数时有先手必胜策略,此时仅需一步取走 \(x - 2\) 个石子即可。然后考虑通过对 \(1\sim a_i\) 求 SG 函数拓展到多堆石子即可。
直接大力枚举转移求 SG 函数显然不行,互质对的数量级会很大。但是发现这个数据范围非常像是给线性筛跑的,于是考虑寻找每个值与其最小质因子的关系。手玩下发现:
- \(\operatorname{SG}(0) = 0\),\(\operatorname{SG}(1) = \operatorname{mex}\{0\} = 1\)。
- 对于所有偶数 \(x\),\(\operatorname{SG}(x) = 0\)。
- 对于所有大于 2 的质数 \(p\),有 \(\forall 1\le i<p, \gcd(p, i) = 1\),则 \(\operatorname{SG}(p) = \operatorname{mex}\{\operatorname{SG}(1), \operatorname{SG}(2), \cdots, \operatorname{SG}(p-1)\} = \operatorname{rank}(p)\),其中 \(\operatorname{rank}(p)\) 代表 \(p\) 在所有质数(包括 2)中的排名。
- 对于所有奇数 \(x\),设其最小质因子为 \(p\),发现有 \(\gcd(x, x - p) \not= 0\),且 \(\forall 1\le y< p, \gcd(i, x) = 1\)。则 \(x\) 第一个不能转移到的位置是 \(p\)。通过观察转移对象可知有 \(\operatorname{SG}(x) = \operatorname{SG}(p)\)。
上述规律赛时是大力手玩到 10 发现的,如果打表的话可以更快发现规律。
于是仅需通过线性筛,对于每个数求得其最小质因子,即可求得所有 \(\operatorname{SG}\) 函数,则仅需判断 \(\operatorname{SG}(a_1)\oplus\cdots \oplus \operatorname{SG}(a_n) \not= 0\) 是否成立即可。
总时间复杂度 \(O\left(v + \sum n\right)\) 级别。
//
/*
By:Luckyblock
https://www.luogu.com.cn/problem/P3383
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e7 +10;
//=============================================================
int n, q;
bool vis[kN];
int pnum, p[kN], sg[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Getprime() {
n = 1e7;
sg[0] = 0, sg[1] = 1;
for (int i = 2; i <= n; ++ i) {
if (i % 2 == 0) sg[i] = 0;
if (!vis[i]) {
p[++ pnum] = i, sg[i] = pnum;
if (i == 2) sg[i] = 0;
}
for (int j = 1; j <= pnum; ++ j) {
if (i * p[j] > n) break;
vis[i * p[j]] = true;
if (i * p[j] % 2 == 1) sg[i * p[j]] = sg[p[j]];
if (i % p[j] == 0) break;
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
Getprime();
int T = read();
while (T --) {
n = read();
int sum = 0;
for (int i = 1; i <= n; ++ i) {
int a = read();
sum ^= sg[a];
}
std::cout << (sum == 0 ? "Bob" : "Alice") << "\n";
}
return 0;
}
F
结论,模型转化,枚举
最后还剩二十分钟本来想下班了,但是看到群友发的玛丽涩图之后灵光一闪就会了,以后应当在赛时就多看玛丽涩图!
首先对于某个区间 \([l, r]\),操作次数上界即合并为同一个数,次数为 \(r-l\)。
如果从回文串等价于两端对应位置相等的角度考虑——会发现题目给定的操作既会影响串的长度又会影响串的元素的值,这很几把麻烦,所以要换个判定回文串的思路:
某个串是回文串的另一个充要条件是:该串从前往后遍历与从后往前遍历对应位置完全相同,这又等价于该串的前缀和与后缀和对应位置完全相同。由于前后缀和一定是单调递增的,发现此时整个值域被前后缀和划分成了若干子区间。对于给定的两种操作:
- 操作 1 将某两个位置合并,可看做选择某个位置 \(i\),并将该位置对应权值 \(v\) 前缀和左侧区间,与后缀和右侧区间的一个权值删除。
- 操作 2 将某个位置分裂,可以看做选择某个位置 \(i\),在该位置对应权值 \(v\) 前缀和右侧区间,与后缀和左侧区间里分别加一个值并满足 \(x+y=b_i\) 的限制。
例如:
发现此时通过操作将区间转化为回文串,即通过上述增删权值的操作,使得前后缀和对应位置完全相同。手玩下发现此时需要的次数实际上即操作次数上界,减去未操作时,前后缀和里相等的权值的个数。
于是考虑先记答案为所有区间操作次数的上界 \(\frac{(n+1)n(n-1)}{6}\),然后考虑减去每个区间的前后缀和中权值相等的个数即可。此时可以通过维护每种权值出现的次数,分别考虑前缀和与后缀和即可。具体实现详见以下江队的提交:https://codeforces.com/contest/2004/submission/276668467。
总时间复杂度 \(O(n^2\log (n^2))\) 级别。
#include<bits/stdc++.h>
#define ll long long
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f
#define pii std::pair<int,int>
typedef int LL;
const signed maxn=(signed)2e3+5;
inline LL Read(){
char ch=getchar();bool f=0;LL x=0;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
if(f==1)x=-x;return x;
}
int ar[maxn],pre[maxn];
int ur[maxn*maxn],tot;
int all[maxn*maxn];
int idx[maxn][maxn];
signed main()
{
LL T=Read();
while (T--){
int n=Read();
for(int i=1;i<=n;++i) ar[i]=Read();
pre[0]=0;
for(int i=1;i<=n;++i) pre[i]=pre[i-1]+ar[i];
tot=0;
for(int i=1;i<=n;++i){
for(int j=i;j<=n;++j){
ur[++tot]=pre[j]-pre[i-1];
}
}
std::sort(ur+1,ur+1+tot);
int cnt=std::unique(ur+1,ur+1+tot)-ur-1;
for(int i=0;i<=cnt;++i) all[i]=0;
for(int i=1;i<=n;++i){
for(int j=i;j<=n;++j){
idx[i][j]=std::lower_bound(ur+1,ur+1+cnt,pre[j]-pre[i-1])-ur;
++all[idx[i][j]];
//printf("idx[%d][%d]=%d\n",i,j,idx[i][j]);
}
}
ll sum=1ll*n*(n*n-1)/6;
//printf("%lld\n",sum);
for(int i=1;i<=n;++i){
for(int j=i;j<=n;++j) --all[idx[i][j]];
for(int j=i;j<=n;++j) sum-=all[idx[i][j]];
//printf("%lld\n",sum);
}
printf("%lld\n",sum);
}
return 0;
}
写在最后
学到了什么:
- E:观察 sg 函数的转移形式,或打表大眼观察 sg 函数的规律。
- F:回文串的多个充要条件。
唉最近忙着准备暑假结训赛没什么时间补题,欠了两场牛客一场 div1+2 没补好爽。
我们人手十分不足的弱校是这样的唉,现在处于一个又想各位牛逼 OI 大神来报考中南大专再度出线 WF 重铸 ACM 辉煌,又不想大家因为各种原因受苦的矛盾心理状态。希望从今年招新开始修补一下吧,希望到时候会有人记得当年有一个叫 Lb 的大傻逼在这里待过。
结尾广告:中南大学 ACM 集训队绝赞招新中!
有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!
没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!
到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜