Educational Codeforces Round 168 (Rated for Div. 2)

A. Strong Password

给定一个字符串,若 sisi1,则 si 的代价为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×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为根的有根树,每个节点有一个初始权值 ai

可以执行任意次如下操作:

选取一个节点 i,使 ai=ai+1,并使它的子树内所有其他节点 jaj=aj1

a1 的最大值。

dp[i] 表示以 i 为根的子树中,最少的数为 dp[i]。(也就是能被 fai 操作 dp[i] 次)

转移时记 mx=max{dp[soni]}

aimx,则 dp[i]=mx

ai<mx,则 dp[i]=ai+mx2

点击查看代码
#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 次询问,每次给定 ki,问当升级间隔为 k 时,第 i 只怪物是否会和Monocarp对决?

容易发现,若第 i 只怪物能在升级间隔为 k 时和Monocarp对决,那么当升级间隔为 k+1 时也一定会和Monocarp对决,具有单调性。

注意到当升级间隔为 k 时,最多升级 nk,总共会升级

i=1nninlogn

次。

fi,j 表示当升级间隔为 i 时,战斗力为 j 的最后一场对决的对手是第 fi,j 只怪物。

转移时,需要找到区间 [fi,j+1,n] 中战斗力大于 j 的第 i 只怪物。若没有则 fi,j+1=n+1

可以权值线段树上二分实现 O(logn) 转移。

具体的,对于战斗力大于 j 的限制,可以通过改变枚举顺序,先枚举 j ,并将小于等于 j 的数在权值线段树上删去。

对于区间左端点的限制,可以用前缀和做差,即先查出区间 [1,fi,j] 中战斗力大于 j 的怪物数 sum,并计算区间 [1,n] 上战斗力大于 j 的第 sum+i 只怪物。

复杂度 O(nlog2n)

我有一个复杂度为 O(nlogn) 整体二分的方法。

没有发现调和级数的性质,只能用单调性解决。

考虑如何求某个位置 i 能对决的最小的升级间隔 k

二分答案,每次 O(n) 判断即可。

考虑整体二分,当二分答案为 [L,R] 时,有三种位置:

1.答案小于 L

2.答案在 [L,R] 中。

3.答案大于 R

如果每次处理复杂度是和第二种位置的数量相关,那么复杂度是可以接受的。

发现第一种一定会对决,第三种一定不会对决,考虑每个数记一个 cnt,表示从前一个第二种位置开始,到当前位置,一共会对决多少次。即两个2位置中间的所有1位置数 + 1(自己)。

发现每次处理只需要 O(len) 遍历就好了,共 logn 层,每层一共遍历 n 次,复杂度 O(nlogn)

nlog2n 代码
#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;
}
nlogn 代码
#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 位置的一根薯条,并在 i1,i2 各放一根薯条。(i3

2.拿走 i1,i2 各一根薯条,并在 i 位置放一根薯条。(i3

3.拿走 1 位置的一根薯条,并在 2 位置放一根薯条。

4.拿走 2 位置的一根薯条,并在 1 位置放一根薯条。

最简形式指,通过任意次操作,使得薯条数最少。

发现可以转化为,从斐波那契数列的前 x 个数中选出可重复的 n 个数,使得它们的加和拆分成最少的斐波那契数的和,且最少个数为 m 个。

预处理 numi 表示和为 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 @   梨愁浅浅  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示