Educational Codeforces Round 168 (Rated for Div. 2)
A. Strong Password
给定一个字符串,若 \(s_i \neq s_{i-1}\),则 \(s_i\) 的代价为2,否则为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 = 15;
int n;
char s[N];
void solve()
{
scanf("%s", s + 1);
n = strlen(s + 1);
int flag = 0;
for(int i = 1; i <= n; ++i)
{
if(!flag && s[i] == s[i - 1])
{
printf("%c", (s[i] - 'a' + 1) % 26 + 'a');
flag = 1;
}
printf("%c", s[i]);
}
if(!flag) printf("%c", (s[n] - 'a' + 1) % 26 + 'a');
printf("\n");
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
B. Make Three Regions
有一个 \(2 \times n\) 的房间,相邻房间可以相互到达,能够相互到达的房间构成一个区域,一些房间不能进入(经过)。保证读入的房间至多有一个区域。
问有多少个可进入的房间满足,删去这个房间后剩余房间构成三个区域?
只有当房间这样分布时,才满足条件。(或倒过来)
0 | 0 | 0 |
---|---|---|
1 | 0 | 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 n;
char s[2][N];
void solve()
{
n = read();
scanf("%s %s", s[0] + 1, s[1] + 1);
int cnt = 0;
for(int i = 2; i < n; ++i)
{
if(s[0][i] == '.' && s[1][i] == '.' && s[1][i - 1] == 'x' && s[1][i + 1] == 'x' && s[0][i - 1] == '.' && s[0][i + 1] == '.') ++cnt;
if(s[1][i] == '.' && s[0][i] == '.' && s[0][i - 1] == 'x' && s[0][i + 1] == 'x' && s[1][i - 1] == '.' && s[1][i + 1] == '.') ++cnt;
}
printf("%d\n", cnt);
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
C. Even Positions
给定一个残缺的长度为 \(n\) 的括号序列。(\(n\) 为偶数)
所有奇数位残缺,填上残缺位使括号序列合法,且代价最小。
括号序列的代价定义为所有括号对的代价,一对括号对的代价定义为左右括号的距离。
对于每一个奇数位,贪心的填括号,如果前面有左括号,那么填右括号和它匹配。否则填右括号。
由于是奇数位,所以前面一定有偶数个左括号,若为0则填右括号。若不为0,则至少有两个左括号,填上右括号不会出现下一个偶数位失配的情况。
点击查看代码
#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;
char s[N];
int n, l, r, q[N];
void solve()
{
n = read();
scanf("%s", s + 1);
l = 1, r = 0;
ll ans = 0;
for(int i = 1; i <= n; ++i)
{
if(s[i] == '_')
{
if(l > r) q[++r] = i;
else ans += i - q[l], ++l;
}else
{
if(s[i] == ')') ans += i - q[l], ++l;
else q[++r] = i;
}
}
printf("%lld\n", ans);
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
D. Maximize the Root
给定一颗以1为根的有根树,每个节点有一个初始权值 \(a_i\)。
可以执行任意次如下操作:
选取一个节点 \(i\),使 \(a_i = a_i + 1\),并使它的子树内所有其他节点 \(j\),\(a_j = a_j - 1\)。
求 \(a_1\) 的最大值。
设 \(dp[i]\) 表示以 \(i\) 为根的子树中,最少的数为 \(dp[i]\)。(也就是能被 \(fa_i\) 操作 \(dp[i]\) 次)
转移时记 \(mx = max\{dp[son_i]\}\)。
若 \(a_i \ge mx\),则 \(dp[i] = mx\)。
若 \(a_i < mx\),则 \(dp[i] = \lfloor \frac{a_i + mx}{2} \rfloor\)。
点击查看代码
#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 n, a[N];
vector<int> V[N];
int dp[N];
void dfs(int k, int fa)
{
dp[k] = a[k];
if(k == 1) dp[k] = 0x7fffffff;
int mn = 0x7fffffff;
for(auto v : V[k])
{
dfs(v, k);
mn = min(mn, dp[v]);
}
if(mn == 0x7fffffff) return ;
if(mn < dp[k]) dp[k] = mn;
else dp[k] = (dp[k] + mn) / 2;
}
void solve()
{
n = read();
for(int i = 1; i <= n; ++i) a[i] = read(), V[i].clear();
for(int i = 2; i <= n; ++i)
{
int fa = read();
V[fa].emplace_back(i);
}
dfs(1, 0);
printf("%d\n", dp[1] + a[1]);
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
E. Level Up
Monocarp在玩游戏,最初战斗力为1,他会依次挑战 \(n\) 只怪物。
若Monocarp的战力严格高于当前怪物,当前怪物会直接逃跑,否则会和Monocarp对决。
Monocarp每对决 \(k\) 次,战斗力会加1。
\(q\) 次询问,每次给定 \(k\) 和 \(i\),问当升级间隔为 \(k\) 时,第 \(i\) 只怪物是否会和Monocarp对决?
容易发现,若第 \(i\) 只怪物能在升级间隔为 \(k\) 时和Monocarp对决,那么当升级间隔为 \(k + 1\) 时也一定会和Monocarp对决,具有单调性。
注意到当升级间隔为 \(k\) 时,最多升级 \(\lfloor \frac{n}{k} \rfloor\),总共会升级
次。
设 \(f_{i, j}\) 表示当升级间隔为 \(i\) 时,战斗力为 \(j\) 的最后一场对决的对手是第 \(f_{i, j}\) 只怪物。
转移时,需要找到区间 \([f_{i, j} + 1, n]\) 中战斗力大于 \(j\) 的第 \(i\) 只怪物。若没有则 \(f_{i, j + 1} = n + 1\)。
可以权值线段树上二分实现 \(O(\log n)\) 转移。
具体的,对于战斗力大于 \(j\) 的限制,可以通过改变枚举顺序,先枚举 \(j\) ,并将小于等于 \(j\) 的数在权值线段树上删去。
对于区间左端点的限制,可以用前缀和做差,即先查出区间 \([1, f_{i, j}]\) 中战斗力大于 \(j\) 的怪物数 \(sum\),并计算区间 \([1, n]\) 上战斗力大于 \(j\) 的第 \(sum + i\) 只怪物。
复杂度 \(O(n \log^2 n)\)
我有一个复杂度为 \(O(n \log n)\) 整体二分的方法。
没有发现调和级数的性质,只能用单调性解决。
考虑如何求某个位置 \(i\) 能对决的最小的升级间隔 \(k\)。
二分答案,每次 \(O(n)\) 判断即可。
考虑整体二分,当二分答案为 \([L, R]\) 时,有三种位置:
1.答案小于 \(L\)。
2.答案在 \([L, R]\) 中。
3.答案大于 \(R\)。
如果每次处理复杂度是和第二种位置的数量相关,那么复杂度是可以接受的。
发现第一种一定会对决,第三种一定不会对决,考虑每个数记一个 \(cnt\),表示从前一个第二种位置开始,到当前位置,一共会对决多少次。即两个2位置中间的所有1位置数 + 1(自己)。
发现每次处理只需要 \(O(len)\) 遍历就好了,共 \(\log n\) 层,每层一共遍历 \(n\) 次,复杂度 \(O(n \log n)\)。
$n \log^2 n$ 代码
#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 n, q, a[N];
vector<int> pos[N];
vector<int> dp[N]; // 设dp[i][j]表示k=i时,第一次达到j时的位置
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
int sum[N << 2];
void update(int k, int l, int r, int pos, int val)
{
sum[k] += val;
if(l == r) return ;
int mid = (l + r) >> 1;
if(pos <= mid) update(ls(k), l, mid, pos, val);
else update(rs(k), mid + 1, r, pos, val);
}
int getpos(int k, int l, int r, int Size)
{
if(l == r) return l;
int mid = (l + r) >> 1;
if(sum[ls(k)] >= Size) return getpos(ls(k), l, mid, Size);
else return getpos(rs(k), mid + 1, r, Size - sum[ls(k)]);
}
int getsum(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return sum[k];
int mid = (l + r) >> 1;
if(R <= mid) return getsum(ls(k), l, mid, L, R);
if(L > mid) return getsum(rs(k), mid + 1, r, L, R);
return getsum(ls(k), l, mid, L, R) + getsum(rs(k), mid + 1, r, L, R);
}
int main()
{
n = read(), q = read();
for(int i = 1; i <= n; ++i)
{
a[i] = read();
pos[a[i]].emplace_back(i);
dp[i].emplace_back(0), dp[i].emplace_back(i);
update(1, 1, n, i, 1);
}
for(int j = 1; j <= n; ++j)
{
for(auto x : pos[j]) update(1, 1, n, x, -1);
for(int i = 1; i * j <= n; ++i)
{
if(dp[i].size() <= j) continue;
if(dp[i][j] == n + 1) continue;
int sum1 = getsum(1, 1, n, 1, dp[i][j]);
int sum2 = getsum(1, 1, n, 1, n);
if(sum2 - sum1 < i){ dp[i].emplace_back(n + 1); continue; }
int pos = getpos(1, 1, n, sum1 + i);
dp[i].emplace_back(pos);
}
}
while(q--)
{
int i = read(), x = read();
if(dp[x].size() <= a[i]) printf("YES\n");
else if(i > dp[x][a[i]]) printf("NO\n");
else printf("YES\n");
}
return 0;
}
$n \log n$ 代码
#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 n, Q;
int a[N], id[N], cnt[N], ans[N], vis[N], b[N];
void solve(int l, int r, int L, int R)
{
if(l > r) return ;
if(L == R){ for(int i = l; i <= r; ++i) ans[id[i]] = L; return ; }
int mid = (L + R) >> 1;
int tmp = 1, num = 0, last1 = 0, last2 = 0, pos1 = l - 1;
for(int i = l; i <= r; ++i)
{
num += cnt[id[i]] - 1;
tmp += num / mid, num %= mid;
if(a[id[i]] >= tmp) vis[id[i]] = 1, b[++pos1] = id[i],
last1 += cnt[id[i]], cnt[id[i]] += last2, last2 = 0,
++num, tmp += num / mid, num %= mid;
else vis[id[i]] = 0, last2 += cnt[id[i]] - 1,
cnt[id[i]] += last1, last1 = 0;
}
int pos2 = pos1;
for(int i = l; i <= r; ++i) if(!vis[id[i]]) b[++pos2] = id[i];
for(int i = l; i <= pos1; ++i) id[i] = b[i];
for(int i = pos1 + 1; i <= pos2; ++i) id[i] = b[i];
solve(l, pos1, L, mid), solve(pos1 + 1, pos2, mid + 1, R);
}
int main()
{
n = read(), Q = read();
for(int i = 1; i <= n; ++i) a[i] = read(), cnt[i] = 1, id[i] = i;
solve(1, n, 1, n);
for(int t = 1; t <= Q; ++t)
{
int y = read(), x = read();
if(ans[y] <= x) printf("YES\n");
else printf("NO\n");
}
return 0;
}
F. Chips on a Line
给定 \(n, x, m\),在 \([1, x]\) 上放 \(n\) 根薯条,使得它的最简形式是 \(m\) 根薯条。
有四种操作:
1.拿走 \(i\) 位置的一根薯条,并在 \(i - 1, i - 2\) 各放一根薯条。(\(i \ge 3\))
2.拿走 \(i - 1, i - 2\) 各一根薯条,并在 \(i\) 位置放一根薯条。(\(i \ge 3\))
3.拿走 \(1\) 位置的一根薯条,并在 \(2\) 位置放一根薯条。
4.拿走 \(2\) 位置的一根薯条,并在 \(1\) 位置放一根薯条。
最简形式指,通过任意次操作,使得薯条数最少。
发现可以转化为,从斐波那契数列的前 \(x\) 个数中选出可重复的 \(n\) 个数,使得它们的加和拆分成最少的斐波那契数的和,且最少个数为 \(m\) 个。
预处理 \(num_i\) 表示和为 \(i\) 最少需要多少个斐波那契数的和。
然后就是一个限制总个数的无限背包 \(DP\)。
设 \(dp[i][j][k]\) 表示只用前 \(i\) 个数,使用了 \(k\) 个,总和为 \(j\) 的方案数,将第一维省去。
很板,感觉不如 \(E\)。
点击查看代码
#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 mod = 998244353;
const int M = 55005, N = 1005;
int n, m, x;
int num[M], fib[50];
void add(int &a, int b){ a = (a + b >= mod) ? (a + b - mod) : (a + b); }
int main()
{
n = read(), x = read(), m = read();
fib[1] = fib[2] = 1;
for(int i = 2; i <= 29; ++i) fib[i] = fib[i - 1] + fib[i - 2];
int max_size = fib[x] * n;
vector< vector<int> > dp(max_size + 1, vector<int>(n + 1));
for(int i = 1; i <= max_size; ++i)
for(int j = 29; j >= 2; --j)
if(i >= fib[j])
{
num[i] = num[i - fib[j]] + 1;
break;
}
dp[0][0] = 1;
for(int i = 1; i <= x; ++i)
for(int j = fib[i]; j <= max_size; ++j)
for(int k = 1; k <= n; ++k)
add(dp[j][k], dp[j - fib[i]][k - 1]);
int ans = 0;
for(int i = 1; i <= max_size; ++i)
if(num[i] == m) add(ans, dp[i][n]);
printf("%d\n", ans);
return 0;
}