Educational Codeforces Round 143 (Rated for Div. 2) 解题记录

A. Two Towers

给定两个包含字符B和R的字符串,可以对它们进行一种操作:将一个字符串的末尾字符移动到另一个字符串的末尾,这种操作可进行若干次。询问是否能使得两个字符串均满足相邻元素不相同(即RBRBRB…或BRBRBR…)

数据范围:题目包含 t ( 1 ≤ t ≤ 1000 ) t(1\le t\le 1000) t(1t1000)组数据,两个字符串长度分别为 n , m ( 1 ≤ n , m ≤ 20 ) n,m(1\le n,m \le 20) n,m(1n,m20)

input

4
4 3
BRBB
RBR
4 7
BRBR
RRBRBRB
3 4
RBR
BRBR
5 4
BRBRR
BRBR

output

YES
YES
YES
NO

思路: 考虑必定不满足题目条件的情况

  1. 任一字符串中存在两个及以上位置相邻元素相同的情况
  2. 任一字符串中存在相邻元素相同的情况,且两个字符串末尾元素相同(此时不能做移动操作)
  3. 两个字符串均存在邻元素相同的情况
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
int check(string& s) {
    int cnt = 0;
    for (int i = 1; i < s.size(); i++) {
        if (s[i] == s[i - 1]) ++cnt;
    }
    return cnt;
}
int main() {
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(0);
    int t;
    cin >> t;
    while (t--) {
        int n, m;
        cin >> n >> m;
        string s1, s2;
        cin >> s1 >> s2;
        if (check(s1) == 0 && check(s2) == 0) {
            cout << "yes" << "\n";
            continue;
        }
        if (check(s1) == 0) {
            if (check(s2) > 1 || s1.back() == s2.back()) {
                cout << "no" << "\n";
                continue;
            }
        }
        if (check(s2) == 0) {
            if (check(s1) > 1 || s1.back() == s2.back()) {
                cout << "no" << "\n";
                continue;
            }
        }
        if (check(s1) > 0 && check(s2) > 0) {
            cout << "no" << "\n";
            continue;
        }
        cout << "yes" << "\n";
    }
}

B. Ideal Point

给定 n n n个区间 [ l , r ] [l,r] [l,r],并对区间 [ l , r ] [l,r] [l,r]中的每个位置进行加一(初始值为0),同时给定位置 k k k,你需要回答是否可以选择 n n n个区间中的任意个,使得操作后位置 k k k的值最大

数据范围:题目包含 t ( 1 ≤ t ≤ 1000 ) t(1\le t\le 1000) t(1t1000)组数据, 1 ≤ n , k ≤ 50 , 1 ≤ l i , r i ≤ 50 1 \le n, k \le 50,1 \le l_i, r_i \le 50 1n,k50,1li,ri50

input

4
4 3
1 3
7 9
2 5
3 6
2 9
1 4
3 7
1 3
2 4
3 5
1 4
6 7
5 5

output

YES
NO
NO
YES

在第一个例子中,第3点已经为最大值3了,所以你不必删除任何区间。

在第四个例子中,你可以删除除 [ 5 , 5 ] [5,5] [5,5]以外的所有区间。

思路: 删除所有不包含 k k k点的区间,使用差分数组进行区间加统计,最后遍历判断是否 k k k点为唯一最大值

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
int main() {
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(0);
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        vector<int> a(100);
        for (int i = 0; i < n; i++) {
            int l, r;
            cin >> l >> r;
            if(l <= k && r >= k) a[l]++, a[r + 1]--;
        }
        int mx = 0, pos = 0;
        bool flag = 1;
        for (int i = 1; i < 100; i++) {
            a[i] += a[i - 1];
            if (a[i] > mx) {
                mx = a[i];
                pos = i;
                flag = true;
            }
            else if (a[i] == mx) flag = 0;
        }
        if (flag && pos == k) cout << "yes" << "\n";
        else cout << "no" << "\n";
    }
}

C. Tea Tasting

给定长度为 n n n的数组 a , b a,b a,b a [ i ] a[i] a[i]表示第 i i i杯茶有多少毫升, b [ i ] b[i] b[i]表示第 i i i个客人的一次能喝多少毫升的茶,初始时第 i i i个客人喝第 i i i杯茶,第二轮时数组左移,第 i + 1 i+1 i+1个客人喝第 i i i杯茶,第1个客人退出,第三轮第 i + 2 i+2 i+2个客人喝第 i i i杯茶,第2个客人退出,以此类推。询问每个客人各喝了多少毫升的茶

数据范围: t ( 1 ≤ t ≤ 1 0 4 ) t(1 \le t \le 10^4) t(1t104)组数据, 1 ≤ n ≤ 2 ⋅ 1 0 5 1 \le n \le 2 \cdot 10^5 1n2105 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109 1 ≤ b i ≤ 1 0 9 1 \le b_i \le 10^9 1bi109,保证 s u m ( n ) ≤ 2 ⋅ 1 0 5 sum(n)\le2 \cdot 10^5 sum(n)2105

image-20230218210308729

input

4
3
10 20 15
9 8 6
1
5
7
4
13 8 5 4
3 4 2 1
3
1000000000 1000000000 1000000000
1 1 1000000000

output

9 9 12 
5 
3 8 6 4 
1 2 2999999997 

思路: i i i杯茶只会被第 i i i个及其之后客人的品尝,记录 b [ i ] b[i] b[i]数组的前缀和 s u m b sum_b sumb,遍历数组 a a a,对于第 i i i杯茶,二分找到最后会被哪一位客人 j j j 品尝,则对于 k ∈ [ i , j − 1 ] k\in[i,j-1] k[i,j1]位客人均品尝了 b [ k ] b[k] b[k]毫升的茶,第 j j j 位客人品尝了 a [ i ] − ( s u m b [ j − 1 ] − s u m b [ i − 1 ] ) a[i]-(sum_b[j-1]-sum_b[i-1]) a[i](sumb[j1]sumb[i1])毫升的茶。 [ i , j − 1 ] [i,j-1] [i,j1]位客人需要进行区间加的操作,于是使用差分数组记录完整品茶的客人,第 j j j 位则客人直接进行累加。

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
int main() {
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(0);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<ll> a(n + 5), b(n + 5), s(n + 5), d(n + 5), ans(n + 5);
        for (int i = 1; i <= n; i++) cin >> a[i];
        for (int i = 1; i <= n; i++) cin >> b[i], s[i] = s[i - 1] + b[i];
        for (int i = 1; i <= n; i++) {
            int l = 1, r = n + 1;
            while (l < r) {
                int mid = (l + r) >> 1;
                if (s[mid] - s[i - 1] >= a[i]) r = mid;
                else l = mid + 1;
            }
            if (l == n + 1) ++d[i];
            else {
                ++d[i];
                --d[l];
                ans[l] += a[i] - (s[l - 1] - s[i - 1]);
            }
        }
        for (int i = 1; i <= n; i++) {
            d[i] += d[i - 1];
            ans[i] += d[i] * b[i];
            cout << ans[i] << " ";
        }
        cout << "\n";
    }
}

D. Triangle Coloring

给你一个由 n n n个顶点和 n n n个边组成的无向图,其中 n n n能被 6 6 6整除。每条边都有一个权重 w i w_i wi,是一个正整数。

该图有如下结构:它被分成 n 3 \frac{n}{3} 3n个边数为3的环,第一个环由顶点 1 , 2 , 3 1,2,3 1,2,3组成,第二个环由顶点 4 , 5 , 6 4,5,6 4,5,6组成,以此类推。不同环的顶点之间没有边。

你必须把这个图的顶点涂成两种颜色,红色和蓝色。每个顶点应该正好有一种颜色,应该正好有 n 2 \frac{n}{2} 2n个红色顶点和 n 2 \frac{n}{2} 2n个蓝色顶点。如果符合这些约束条件,那么这种着色就被称为有效。

着色的权重是连接不同颜色的两个顶点的边的权重之和。

W W W为有效着色的最大可能权重。计算具有 W W W权重的有效着色的方案有多少种,并将方案数以 998244353 998244353 998244353为模数打印出来。

数据范围: 6 ≤ n ≤ 3 ⋅ 1 0 5 6 \le n \le 3 \cdot 10^5 6n3105,保证 n % 6 = 0 n\%6=0 n%6=0 w 1 , w 2 , … , w n , 1 ≤ w i ≤ 1000 w_1, w_2, \dots, w_n,1 \le w_i \le 1000 w1,w2,,wn,1wi1000

input

12
1 3 3 7 8 5 2 2 2 2 4 2

output

36

input

6
4 2 6 6 6 4

output

2

image-20230218212400447

思路: 每个环最多贡献两条边的权重,此时环染色为蓝蓝红或者红红蓝,且要使图正好有 n 2 \frac{n}{2} 2n个红色顶点和 n 2 \frac{n}{2} 2n个蓝色顶点,则染色为蓝蓝红和红红蓝的环各占 n 6 \frac{n}{6} 6n个,要使权重之和最大,则不可能会有红红红或者蓝蓝蓝的环。所以所有环的染色情况为 C n 3 n 6 C_{\frac{n}{3}}^{\frac{n}{6}} C3n6n种,对于每一个环,需要选择最大的两条边,若三条边的权重 e 1 = e 2 = e 3 e_1=e_2=e_3 e1=e2=e3,则有三种选法;若三条边的权重 e 1 = e 2 < e 3 e_1=e_2<e_3 e1=e2<e3,则有两种选法。由乘法原理将每个环的颜色选法相乘,最后乘上 C n 3 n 6 C_{\frac{n}{3}}^{\frac{n}{6}} C3n6n即可。

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5, mod = 998244353;
typedef long long ll;
ll qmi(int a, int k, int p)
{
    ll res = 1;
    while (k)
    {
        if (k & 1)res = (ll)res * a % p;
        a = (ll)a * a % p;
        k >>= 1;
    }
    return res;
}

ll C(int a, int b, int p)
{
    if (b > a)return 0;
    ll res = 1;
    for (int i = 1, j = a; i <= b; i++, j--)
    {
        res = res * j % p;
        res = res * qmi(i, p - 2, p) % p;
    }
    return res;
}
ll lucas(int a, int b, int p)
{
    if (a < p && b < p)return C(a, b, p);
    return (ll)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main() {
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(0);
    int t = 1;
    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n + 1);
        for (int i = 1; i <= n; i++) cin >> a[i];
        ll ans = 1;
        for (int i = 1; i <= n; i++) {
            if (i % 3 == 0) {
                int b[3] = { a[i - 2], a[i - 1], a[i] };
                sort(b, b + 3);
                if (b[0] == b[1] && b[1] == b[2]) ans = (ans * 3) % mod;
                if (b[0] == b[1] && b[1] != b[2]) ans = (ans * 2) % mod;
            }
        }
        ans = (ans * lucas(n / 3, n / 6, mod)) % mod;
        cout << ans << "\n";
    }
}

E. Explosions?

给定一个长度为 n n n数组 a a a,每个位置 i i i 有一个怪物 a [ i ] a[i] a[i],你有一个基本法术,消耗 1 M P 1MP 1MP,可以将 a [ i ] a[i] a[i]的生命值减一( a [ i ] > 0 a[i]>0 a[i]>0时使用),该法术你可以施放任意次。另外,你有一个特殊的卷轴,上面有 "爆炸 "法术,你只能使用一次。爆炸将消耗 a [ i ] a[i] a[i] M P MP MP,直接将 a [ i ] a[i] a[i]的生命值清零,并对相邻元素造成溅射伤害,伤害值为 a [ i ] − 1 a[i]-1 a[i]1,若爆炸造成的伤害足以杀死 i − 1 i-1 i1(或 i + 1 i+1 i+1)的怪物,即当前的 a [ i − 1 ] ≤ a [ i ] − 1 a[i-1]\le a[i]-1 a[i1]a[i]1(或 a [ i + 1 ] ≤ a [ i ] − 1 a[i+1]\le a[i]-1 a[i+1]a[i]1),那么该怪物也会死亡,产生二次爆炸,伤害为 a [ i − 1 ] − 1 a[i-1]-1 a[i1]1 a [ i + 1 ] − 1 a[i+1]-1 a[i+1]1 以此类推,直到爆炸结束。询问杀死所有怪物,你需要的最低总MP是多少?

数据范围: t ( 1 ≤ t ≤ 1 0 4 ) t(1 \le t \le 10^4) t(1t104)组数据, 1 ≤ n ≤ 3 ⋅ 1 0 5 1 \le n \le 3 \cdot 10^5 1n3105 1 ≤ a [ i ] ≤ 1 0 6 1 \le a[i] \le 10^6 1a[i]106,保证 s u m ( n ) ≤ 3 ⋅ 1 0 5 sum(n)\le3 \cdot 10^5 sum(n)3105

input

5
3
1 1 1
4
4 1 2 1
4
5 10 15 10
1
42
9
1 2 3 2 2 2 3 2 1

output

3
6
15
42
12

思路: 要想做到实施爆破操作后全图清空,首先要使用基本法术将数组处理为 “山峰“ (严格递增后严格递减)。由题易得,我们需要得到对每一个 a [ i ] a[i] a[i]计算进行爆破后清空全图的代价,取其中的最小值。我们使用一个严格单调递增的单调栈,栈中每个元素为一个二元组 ( v a l , c n t ) (val,cnt) (val,cnt)示为一个集合,集合中有 c n t cnt cnt个数,最大值为 v a l val val,最小值为 v a l − c n t + 1 val - cnt + 1 valcnt+1(最小值 ≥ 0 \ge0 0),它们严格递增且差值为1,遍历数组 a a a

  • 若当前集合的最小值 v a l − c n t + 1 val-cnt+1 valcnt+1大于栈顶元素的 v a l val val,则直接插入 ( a [ i ] , 1 ) (a[i],1) (a[i],1)
  • 若当前集合的最小值 v a l − c n t + 1 ≤ val-cnt+1\le valcnt+1栈顶元素的 v a l val val,则不断出栈,进行集合的合并,并计算合并的代价

以下图为例: a = [ 1 , 2 , 3 , 4 , 3 , 4 , 5 , 2 ] a=[1,2,3,4,3,4,5,2] a=[1,2,3,4,3,4,5,2]

image-20230219102400781

当前进栈元素集合的最小值为3, ≤ \le 栈顶元素的 v a l val val,出栈,集合合并,代价为 4 − 2 = 2 4-2=2 42=2

image-20230219125426989

当前进栈元素集合的最小值为2, ≤ \le 栈顶元素的 v a l val val,出栈,集合合并,代价为 3 − 1 = 2 3-1=2 31=2

image-20230219125519438

当前进栈元素集合的最小值为1, ≤ \le 栈顶元素的 v a l val val,出栈,集合合并,代价为 2 − 0 = 2 2-0=2 20=2

image-20230219125556336

当前进栈元素集合的最小值为0, ≤ \le 栈顶元素的 v a l val val,出栈,集合合并,代价为 1 − 0 = 1 1-0=1 10=1

image-20230219141731990

此时集合中的元素为 { 0 , 0 , 1 , 2 , 3 } \{0,0,1,2,3\} {0,0,1,2,3}

后面进栈 3,4,5,2 如法炮制即可

image-20230219141859746

注意 ( 2 , 3 ) (2,3) (2,3) ( 3 , 5 ) (3,5) (3,5)合并时, ( 2 , 3 ) (2,3) (2,3)的集合元素是 { 0 , 1 , 2 } \{0,1,2\} {0,1,2} ( 3 , 5 ) (3,5) (3,5)的集合元素是 { 0 , 0 , 1 , 2 , 3 } \{0,0,1,2,3\} {0,0,1,2,3},合并代价为 1 + 2 + 3 = 6 1+2+3=6 1+2+3=6,使用等差数列求和公式计算代价。两个集合合并后为 { 0 , 0 , 0 , 0 , 0 , 0 , 1 , 2 } \{0,0,0,0,0,0,1,2\} {0,0,0,0,0,0,1,2}

image-20230219142415270

由此便可得到将数组的前缀 [ 1 , i ] , i ∈ [ 1 , n ] [1,i],i\in[1,n] [1,i],i[1,n]处理为严格递增所需的代价 L [ i ] L[i] L[i]。同理,我们将数组反转再跑一次单调栈便可得到将后缀 [ i , n ] , i ∈ [ 1 , n ] [i,n],i\in[1,n] [i,n],i[1,n]处理为严格递减所需的代价 R [ i ] R[i] R[i],所以对每一个 a [ i ] a[i] a[i]计算进行爆破后清空全图的代价即为 L [ i ] + R [ i ] + a [ i ] L[i]+R[i]+a[i] L[i]+R[i]+a[i]
a n s = m i n i ∈ [ 1 , n ] { L [ i ] + R [ i ] + a [ i ] } ans=\mathop{min}\limits_{i\in[1,n]}\{L[i]+R[i]+a[i]\} ans=i[1,n]min{L[i]+R[i]+a[i]}

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5, mod = 998244353;
typedef long long ll;
ll calc(ll x, ll cnt) {
    ll st = x - cnt + 1;
    return cnt * (st + x) / 2;
}
int main() {
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(0);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<ll> a(n + 1), L(n + 1), R(n + 1);
        for (int i = 1; i <= n; i++) cin >> a[i];
        auto solve = [&](vector<ll>& cost) {
            vector<pair<ll, ll>> st;
            ll sum = 0;
            for (int j = 1; j <= n; j++) {
                ll cnt = 1;
                while (st.size() && st.back().first > a[j] - cnt) {
                    // 等差求和计算代价
                    int mi = max(0ll, a[j] - cnt);	// 集合中最小值不小于0
                    sum += calc(st.back().first, min(st.back().second, st.back().first))
                        - calc(mi, min(mi, st.back().second));
                    // 对(val = 3, cnt = 6)等差求和,实际上是0+0+0+1+2+3,所以cnt = min(cnt, val)
                    // 集合合并
                    cnt += st.back().second;
                    st.pop_back();
                }
                cost[j] = sum;
                st.push_back({ a[j], cnt });
            }
        };
        solve(L);
        reverse(a.begin() + 1, a.end());
        solve(R);
        reverse(R.begin() + 1, R.end());
        reverse(a.begin() + 1, a.end());
        ll ans = (1ll << 60);
        for (int i = 1; i <= n; i++) {
            ans = min(ans, L[i] + R[i] + a[i]);
        }
        cout << ans << "\n";
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"educational codeforces round 103 (rated for div. 2)"是一个Codeforces平台上的教育性比赛,专为2级选手设计评级。以下是有关该比赛的回答。 "educational codeforces round 103 (rated for div. 2)"是一场Codeforces平台上的教育性比赛。Codeforces是一个为程序员提供竞赛和评级的在线平台。这场比赛是专为2级选手设计的,这意味着它适合那些在算法数据结构方面已经积累了一定经验的选手参与。 与其他Codeforces比赛一样,这场比赛将由多个问题组成,选手需要根据给定的问题描述和测试用例,编写程序来解决这些问题。比赛的时限通常有两到三个小时,选手需要在规定的时间内提交他们的解答。他们的程序将在Codeforces的在线评测系统上运行,并根据程序的正确性和效率进行评分。 该比赛被称为"educational",意味着比赛的目的是教育性的,而不是针对专业的竞争性。这种教育性比赛为选手提供了一个学习和提高他们编程技能的机会。即使选手没有在比赛中获得很高的排名,他们也可以从其他选手的解决方案中学习,并通过参与讨论获得更多的知识。 参加"educational codeforces round 103 (rated for div. 2)"对于2级选手来说是很有意义的。他们可以通过解决难度适中的问题来测试和巩固他们的算法和编程技巧。另外,这种比赛对于提高解决问题能力,锻炼思维和提高团队合作能力也是非常有帮助的。 总的来说,"educational codeforces round 103 (rated for div. 2)"是一场为2级选手设计的教育性比赛,旨在提高他们的编程技能和算法能力。参与这样的比赛可以为选手提供学习和进步的机会,同时也促进了编程社区的交流与合作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值