文章目录
竞赛首页 Codeforces Round #668 (Div. 2)
A - Permutation Forgery
题意
找到一个另一个序列不同于给出的序列,使的该序列得到的所有 a [ i − 1 ] + a [ i ] ( i > 1 ) a[i-1] + a[i](i > 1) a[i−1]+a[i](i>1),排序后与给定的序列得到的 a [ i − 1 ] + a [ i ] ( i > 1 ) a[i-1] + a[i](i > 1) a[i−1]+a[i](i>1) 排序后相同。
序列中的每个值是不同的
分析
其实可以发现,只要把给出的序列逆向输出,就可以满足所要的条件
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100 + 5;;
const ll mod = 1e9 + 7;
int a[maxn];
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for(int i = n; i >= 1; --i)
printf("%d%c", a[i], i == 1 ? '\n' : ' ');
}
return 0;
}
B - Array Cancellation
题意
给出长度为 n n n 的序列,满足 ∑ a i = 0 \sum a_i = 0 ∑ai=0
可以对数列进行操作
对于索引 i , j ( 1 ≤ i , j ≤ n , i ≠ j ) i, j(1 \leq i, j \leq n, i \not= j) i,j(1≤i,j≤n,i=j),令 a [ i ] = a [ i ] + 1 , a [ j ] = a [ j ] − 1 a[i] = a[i]+1, a[j] = a[j]-1 a[i]=a[i]+1,a[j]=a[j]−1
若 i < j i < j i<j,则该操作无需花费
否则,一次操作需要 1 1 1 点花费
问要使数组变成全为 0 0 0 的序列,至少需要多少花费
分析
对于所有 a [ j ] < 0 a[j] < 0 a[j]<0 的数,先用索引在 j j j 之前的数相抵
则剩余的数值中, ∑ a [ i ] ( a [ i ] > 0 ) \sum a[i] (a[i] > 0) ∑a[i](a[i]>0) 就是最小的花费
当然求 ∑ a [ i ] ( a [ i ] < 0 ) \sum a[i](a[i]<0) ∑a[i](a[i]<0) 也是可以的
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;;
const ll mod = 1e9 + 7;
int a[maxn];
queue<int> q;
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n;
scanf("%d", &n);
while(!q.empty()) q.pop();
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for(int i = 1; i <= n; ++i) {
while(a[i] < 0 && !q.empty()) {
int top = q.front(); q.pop();
if(a[i] + top < 0) a[i] += top;
else q.push(top + a[i]), a[i] = 0;
}
if(a[i] > 0) q.push(a[i]);
}
ll ans = 0;
while(!q.empty()) {
ans += 1ll * q.front(); q.pop();
}
printf("%lld\n", ans);
}
return 0;
}
C - Balanced Bitstring [思维]
题意
给出一个长度为 n n n,只包含字符 0,1,? 的字符串
字符 ? 可以转换成 0,1 中的任意一个字符
问改串是否能满足在任意长度为 k k k 的连续子串中,0,1 的个数相同
分析
由于任意长度为 k k k 的连续子串,都要满足个数相同,比如 k = 4 k=4 k=4
[1, 0, ?, 1], ?,下一个串就是 1, [0, ?, 1, ?],可以发现相距为 k k k 的字符必须要是一样的,才可以满足题目所要求的
除此之外,还需要判断第一个长度为 k k k 的串是否可以满足 0,1 个数相同
只要 0,1 其中任意一个的个数不会超过一半,或者说其他两个字符个数和即可
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 5;;
const ll mod = 1e9 + 7;
char s[maxn];
int cnt[3];
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n, k;
scanf("%d%d%s", &n, &k, s + 1);
for(int i = 1; i <= n; ++i) {
if(s[i] == '?') s[i] = '2';
}
bool flag = true;
for(int i = 1; i <= k; ++i) {
cnt[0] = cnt[1] = cnt[2] = 0;
for(int j = i; j <= n; j += k) cnt[s[j]-'0']++;
if(cnt[0] && cnt[1]) {
flag = false;
break;
}else if(cnt[0]) {
for(int j = i; j <= n; j += k) s[j] = '0';
}else if(cnt[1]) {
for(int j = i; j <= n; j += k) s[j] = '1';
}
}
cnt[0] = cnt[1] = cnt[2] = 0;
for(int i = 1; i <= k; ++i) cnt[s[i]-'0']++;
if(cnt[0] > k/2 || cnt[1] > k/2) flag = false;
puts(flag ? "YES" : "NO");
}
return 0;
}
D - Tree Tag [博弈][dfs][树的直径] ★★
题意
给一棵树,Alice从 a a a 出发,每次移动最大可移动距离为 d a da da,Bob从 b b b 出发。每次移动的最大可移动距离为 d b db db
可移动距离即为路径上的边数
他们两个都可以跳无限步,且轮流跳,Alice先手。若 Alice 和 Bob 最终会跳到同一个点上,则为 Alice 赢,否则为 Bob 赢
分析
参考博客
令 d e p a b depab depab 为 a , b a,b a,b 简单路径的距离; l e n len len 为树的直径; 比较容易想到的几个点是
- d e p a b ≤ d a depab \leq da depab≤da,显然这种情况下, A A A 可以直接跳到 B B B 的初始位置,所以是 Alice 赢
- d b > 2 × d a db > 2 \times da db>2×da,这种情况下,不管第一次 A A A 跳到哪, B B B 只要保持与 A A A 距离超过 d a da da 的点就可以不被抓到,所以是 Bob 赢
但是在 2 的情况下,有一个问题是 B B B 如果没办法跳到那么远的点,即没有那么远的点可以跳,那么 B B B 无法逃离 A A A 的魔爪
既然要求可以跳的距离,那么可以想到树的直径,直径长度即左右叶子结点的最大距离
如果这个最大距离 l e n len len 满足 l e n ≤ 2 × d a len \leq 2 \times da len≤2×da,那么 A A A 可以一次性跳到树的直径的中间,不管 B B B 跑到哪个地方, A A A 都可以一次抓到
当然这个判断要在 2 之前
除去这些还剩下 d b ≤ 2 × d a db \leq 2 \times da db≤2×da 这种情况,显然 A A A 也能与 B B B 在同一个点(具体可看参考博客,他写的很详细)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;
vector<int> ed[maxn];
int dep[maxn];
int depab;
void dfs(int u, int fa, int deep, int b) {
if(u == b) {
depab = deep;
return ;
}
for(auto v : ed[u]) {
if(v == fa) continue;
dfs(v, u, deep+1, b);
}
}
void dfs2(int u, int fa, int deep) {
dep[u] = deep;
for(auto v : ed[u]) {
if(v == fa) continue;
dfs2(v, u, deep+1);
}
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n, a, b, da, db;
scanf("%d%d%d%d%d", &n, &a, &b, &da, &db);
for(int i = 1; i <= n; ++i) ed[i].clear();
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
ed[u].push_back(v), ed[v].push_back(u);
}
int rt;
dfs(a, 0, 0, b);
if(da >= depab) {
puts("Alice");
}else{
rt = 1;
dfs2(rt, rt, 0);
for(int i = 1; i <= n; ++i) {
if(dep[rt] < dep[i]) rt = i;
}
dfs2(rt, rt, 0);
for(int i = 1; i <= n; ++i) {
if(dep[rt] < dep[i]) rt = i;
}
// printf("in -- %d\n", dep[rt]);
if(2 * da >= dep[rt]) puts("Alice");
else if(db > 2 * da) puts("Bob");
else puts("Alice");
}
}
return 0;
}
E - Fixed Point Removal [线段树] ★★★
题意
给一个长度为 n n n 的序列, m m m 次询问
每次询问给出 x , y x, y x,y,禁用掉该序列前 x x x 个数和最后 y y y 个数
对于每个 a [ i ] = = i a[i] == i a[i]==i 的位置,可以删去这个位置上的数,问数组最大可以删除数的次数
分析
参考博客
首先可以明确的是, a [ i ] = = i a[i] == i a[i]==i,就是可以删除的
a [ i ] > i a[i] > i a[i]>i,这个是不管如何都无法删除的
a [ i ] < i a[i] < i a[i]<i,这个需要令前面删除 a [ i ] − i a[i]-i a[i]−i 个数才可以删去这个数
第一二个点都好解决,问题是第三个点(废话,一时没想到怎么解决
第三个点换个方式说,也就是对于其前面的 a [ i ] − i a[i]-i a[i]−i 个可删去的数,都是无法给他们增加贡献的,但可以给更前面的数都增加 1 1 1 的贡献
是不是有点区间的感觉
由于值记录可以删除的数,那么这个删去的数组必然是非递减的
只要维护 a [ i ] a[i] a[i] 可以增加贡献的位置,然后求区间和,就可以得到结果
代码
#include <bits/stdc++.h>
#define lson id<<1
#define rson id<<1|1
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 3e5 + 5;
const ll mod = 1e9 + 7;
vector<pair<int, int> > q[maxn];
int tree[maxn << 2];
int res[maxn];
int a[maxn];
void update(int id, int l, int r, int pos, int val) {
if(l == pos && r == pos) {
tree[id] += val;
return ;
}
int mid = (l + r) >> 1;
if(pos <= mid) update(lson, l, mid, pos, val);
else update(rson, mid+1, r, pos, val);
tree[id] = tree[lson] + tree[rson];
}
int kth(int id, int l, int r, int k) { // 区间第 k 大
if(l == r) return l;
int mid = (l + r) >> 1;
if(tree[rson] >= k)
return kth(rson, mid+1, r, k);
else
return kth(lson, l, mid, k-tree[rson]);
}
int query(int id, int l, int r, int ql, int qr) {
if(ql <= l && qr >= r) return tree[id];
int mid = (l + r) >> 1, ans = 0;
if(ql <= mid) ans += query(lson, l, mid, ql, qr);
if(qr > mid) ans += query(rson, mid+1, r, ql, qr);
return ans;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
a[i] = i >= a[i] ? i - a[i] : inf;
}
for(int i = 1, l, r; i <= m; ++i) {
scanf("%d%d", &l, &r);
q[n-r].push_back(make_pair(l + 1, i));
}
memset(tree, 0, sizeof(tree));
for(int i = 1, ans = 0; i <= n; ++i) { // 包含开头禁用的序列长度
if(a[i] == 0) {
update(1, 1, n, i, 1);
ans++;
}else if(ans >= a[i]) {
update(1, 1, n, kth(1, 1, n, a[i]), 1);
ans++;
}
for(pair<int, int> j : q[i])
res[j.second] = query(1, 1, n, j.first, n);
}
for(int i = 1; i <= m; ++i) printf("%d\n", res[i]);
return 0;
}
补完撒花✿✿ヽ(°▽°)ノ✿