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\),总共会升级

\[\sum_{i = 1}^{n} \lfloor \frac{n}{i} \rfloor \sim n \log n \]

次。

\(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;
}
posted @ 2024-09-26 00:17  梨愁浅浅  阅读(13)  评论(0编辑  收藏  举报