Codeforces Round 961 (Div. 2)

比赛链接:https://codeforces.com/contest/1995

A. Diagonals

题意

给一个 n ∗ n n*n nn 的棋盘,要放上 k k k 个棋子,问最少被放棋子的正对角线有多少条?

数据范围

1 ≤ n ≤ 100 , 0 ≤ k ≤ n 2 1 \le n \le 100, 0 \le k \le n^2 1n100,0kn2

思路

正对角线有 2 n − 1 2n-1 2n1 条,能放的棋子数从大到小依次为 n , n − 1 , n − 1 , n − 2 , n − 2 , . . . 1 , 1 n,n-1,n-1,n-2,n-2,...1,1 n,n1,n1,n2,n2,...1,1,即棋子数属于 [ 1 , n − 1 ] [1,n-1] [1,n1] 的对角线有两条,棋子数为 n n n 的对角线有一条。

要使得被放棋子的对角线尽可能少,可以把棋子从最长的对角线开始放,直到棋子放完为止。

复杂度

时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)

代码实现

// Problem: A. Diagonals
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/A
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;

#define int long long

void solve()
{
    int n, k;
    cin >> n >> k;
    // 对角线按从大到小排
    vector<int> arr = { n };
    for (int i = n - 1; i >= 1; i--) {
        arr.push_back(i);
        arr.push_back(i);
    }
    // 特判 k = 0 的情况
    if (k == 0) {
        cout << 0 << '\n';
        return;
    }
    int ans = 0;

    for (int i : arr) {
        if (k > i) {
            ans++;
            k -= i;
        } else {
            ans++;
            break;
        }
    }
    cout << ans << '\n';
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}

B. Bouquet

题意

n n n 种花,每种花的花瓣数不同,购买花需要的金币与购买的花的花瓣总数相同,购买的花的两两之间的花瓣数不能超过 1 1 1

问有 m m m 枚金币能买的花瓣总数是多少?

数据范围

1 ≤ n ≤ 2 ⋅ 1 0 5 , 1 ≤ m ≤ 1 0 18 1 \le n \le 2 \cdot 10^5, 1 \le m \le 10^{18} 1n2105,1m1018
1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109 (每种花的花瓣数)
1 ≤ c i ≤ 1 0 9 1 \le c_i \le 10^9 1ci109 (每种花的数量)

思路

如果当前购买的花的花瓣数为 x x x,该种花的数量为 y y y,那么该种花能最多购买的数量 k = m i n ( y , ⌊ m x ⌋ ) k =min(y,\lfloor \frac{m}{x} \rfloor) k=min(y,xm⌋)

如果花瓣数为 x + 1 x+1 x+1,数量为 y 1 y_1 y1,那么可以再购买花瓣数为 x + 1 x+1 x+1 的花的数量 k 1 = m i n ( y 1 , ⌊ m − k ∗ x x + 1 ⌋ ) k_1 = min(y_1,\lfloor \frac{m-k*x}{x+1} \rfloor) k1=min(y1,x+1mkx⌋)

经过上面的购买后,得到的花瓣总数为 k ∗ x + k 1 ∗ ( x + 1 ) = ( k + k 1 ) ∗ x + k 1 k*x + k_1*(x+1)=(k+k_1)*x+k_1 kx+k1(x+1)=(k+k1)x+k1,剩下的金币为 m − ( k + k 1 ) ∗ x − k 1 m-(k+k_1)*x-k_1 m(k+k1)xk1,要使得花瓣总数再增加,考虑把花瓣数为 x x x 的花替换成花瓣数为 x + 1 x+1 x+1 的花。

因为替换的花数量不能超过购买的花瓣数为 x x x 花的数量 k k k 和还没被购买的 x + 1 x+1 x+1 的花的数量 y 1 − k 1 y_1-k_1 y1k1,且多出来的花费也不能超过剩下的金币数 m − ( k + k 1 ) ∗ x − k 1 m-(k+k_1)*x-k_1 m(k+k1)xk1,所以最多能对 m i n ( k , y 1 − k 1 , m − ( k + k 1 ) ∗ x − k 1 ) min(k,y_1-k_1,m-(k+k_1)*x-k_1) min(k,y1k1,m(k+k1)xk1) 朵花进行替换。

综上所述,购买花瓣数为 x x x 的花的最多花瓣总数为 k + k 1 + m i n ( k , y 1 − k 1 , m − ( k + k 1 ) ∗ x − k 1 ) k+k_1 + min(k,y_1-k_1,m-(k+k_1)*x-k_1) k+k1+min(k,y1k1,m(k+k1)xk1),对每种花计算最多花瓣总数取最大值即为答案。

因为给出每种花的花瓣数不保证是有序的,所以为方便计算花瓣总数,可以读入后按花瓣数进行排序,或者直接用 m a p / u n o r d e r e d _ m a p map/unordered\_map map/unordered_map 存下每种特定花瓣数的花的数量。

复杂度

空间复杂度均为 O ( n ) O(n) O(n),时间复杂度为 O ( n ) O(n) O(n) (用哈希表存)或者 O ( n log ⁡ n ) O(n \log n) O(nlogn) (排序或者用 m a p map map 存)

代码实现

// Problem: B2. Bouquet (Hard Version)
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/B2
// Memory Limit: 256 MB
// Time Limit: 1500 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N = 1e6 + 5;

int n, m;
int a[N], c[N];
map<int, int> cnt;

void solve()
{
    cin >> n >> m;
    cnt.clear();
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        cnt[a[i]] = x;
    }
    int ans = 0;
    for (auto it : cnt) {
        int x = it.first, y = it.second;
        int k = min(m / x, y);
        int cur = x * k;
        // 当前能取得的最大花瓣总数
        int cost = m - x * k;
        if (cnt.count(x + 1)) {
            int y1 = cnt[x + 1];
            int k1 = min(cost / (x + 1), y1);
            cur += k1 * (x + 1);
            cost -= k1 * (x + 1);
            // 购买后的剩下金币
            cur += min({ cost, y1 - k1, k });
        }
        ans = max(ans, cur);
    }
    cout << ans << '\n';
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}

C. Squaring

题意

给一个长度为 n n n 的数组 a a a,每次操作可以把数组中的一个数变成这个数平方之后的结果,问将数组 a a a 变成不递减的最小操作次数。

数据范围

1 ≤ n ≤ 2 ⋅ 1 0 5 1 \le n \le 2 \cdot 10 ^5 1n2105 1 ≤ a i ≤ 1 0 6 1 \le a_i \le 10 ^ 6 1ai106

思路

注意到 1 1 1 比较特殊,因为 1 1 1 平方后仍然为 1 1 1,所以如果存在 a i − 1 > a i a_{i-1}>a_i ai1>ai a i = 1 a_i=1 ai=1,此时一定无解,因为无法对 a i a_i ai 进行操作使得 a i ≥ a i − 1 a_i \ge a_{i-1} aiai1,除此之外均有解。

如果前 i − 1 i-1 i1 个数已通过最少操作次数变成不递减,此时把 a i a_{i} ai 操作到恰好不小于 a i − 1 a_{i-1} ai1,增加的的操作次数最少,这就得到把前 i i i 个数变成不递减的最小操作次数。

发现这存在着从左到右的递推关系,因为第一个数不需要操作,所以从 2 2 2 n n n 进行操作,统计到每个数的最少操作次数总和即为答案。

一个数 x x x 经过 y y y 次平方操作,会变成 x 2 y x^{2^y} x2y,因为每次操作都是在指数上多乘上一个 2 2 2

如果 a i − 1 a_{i-1} ai1 经过 b i − 1 b_{i-1} bi1 次平方操作,那么 a i − 1 a_{i-1} ai1 会变成 a i − 1 2 b i − 1 a_{i-1}^{2^{b_{i-1}}} ai12bi1,可以通过二分查找找到最小的 a i 2 x a_{i}^{2^x} ai2x 满足 a i 2 x ≥ a i − 1 2 b i − 1 a_{i}^{2^x} \ge a_{i-1}^{2^{b_{i-1}}} ai2xai12bi1 x x x 即为 a i a_{i} ai 需要进行的最小操作次数。

注意到在上面的式子中,指数可能会很大导致超出数据范围,因此需要考虑怎么变式使得可以在数据范围内进行大小比较。

涉及指数可以把指数形式化为对数形式,对两边取对数可以得到 2 x ∗ log ⁡ a i ≥ 2 b i − 1 ∗ log ⁡ a i − 1 2^x *\log a_i \ge 2^{b_{i-1}} *\log a_{i-1} 2xlogai2bi1logai1

因为 2 x 2^x 2x 也可能很大,所以对上面的式子再次两边取对数,得到 x ∗ log ⁡ 2 + log ⁡ ( log ⁡ a i ) ≥ b i − 1 ∗ log ⁡ 2 + log ⁡ ( log ⁡ a i − 1 ) x*\log 2+\log(\log a_i) \ge b_{i-1}*\log 2 + \log(\log a_{i-1}) xlog2+log(logai)bi1log2+log(logai1),这样就可以进行比较了。

细节

注意浮点数运算会有误差,判断两个浮点数是否相等,需要比较它们的差值是否在规定的最小差值内,如果不超过则视为相等,该最小差值不能大于 1 0 − 8 10^{-8} 108

复杂度

空间复杂度 O ( n ) O(n) O(n),时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码实现

// Problem: C. Squaring
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/C
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N = 2e5 + 5;

int n;
int a[N], b[N];

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        b[i] = 0;
    }
    for (int i = 2; i <= n; i++) {
        // 无解的情况
        if (a[i] == 1 && a[i - 1] != 1) {
            cout << -1 << '\n';
            return;
        }
    }
    for (int i = 2; i <= n; i++) {
        int l = 0, r = 2e5;
        long double lx = b[i - 1] * log(2) + log(log(a[i - 1]));
        // lx 为 a[i-1] 进行最小操作后的结果(对数化后的)
        while (l < r) {
            int mid = (l + r) >> 1;
            long double rx = mid * log(2) + log(log(a[i]));
            if (lx <= rx || fabs(lx - rx) <= 1e-8)
                r = mid;
            else
                l = mid + 1;
        }
        b[i] = r;
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        ans += b[i];
        // b[i] 为 a[i] 的最小操作次数
    }
    cout << ans << '\n';
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}

D. Cases

题意

给一个由 c c c 种字符组成的,长度为 n n n 的字符串,要将字符串分割成长度不超过 k k k 的若干子串,问子串的结尾字符最少有多少种。

数据范围

1 ≤ k ≤ n ≤ 2 18 1 \le k \le n \le 2^{18} 1kn218, 1 ≤ c ≤ 18 1 \le c \le 18 1c18

思路

要将字符串分割成长度不超过 k k k 的若干子串,意味着对于任意一种合法结尾字符集合 S S S,每个长度为 k k k 的子区间至少有该集合的一个结尾字符。

因为如果存在着长度为 k k k 的子区间不含 S S S 中结尾字符,那么该区间会包含在长度大于 k k k 的子串,集合 S S S 就不合法了。

f T f_T fT 为不包含一些结尾字符的字符集合 T T T 是否合法, f T = 1 f_T = 1 fT=1 表示合法, f T = 0 f_T = 0 fT=0 反之。

1,因为对于每个长度为 k k k 的子区间至少存在一个结尾字符在区间内,所以任意一个长度为 k k k 的子区间的包含的字符集合为 S S S,不包含 S S S 的所有字符显然是不合法的,故 f S = 0 f_S = 0 fS=0

统计每个长度为 k k k 的子区间包含的字符,相当于维护一个长度为 k k k 的窗口,用双指针维护,具体做法是开 c c c 个桶统计每种字符的数量 c n t i cnt_i cnti,每次进入下一个子区间,就把新进入的字符数量加一,统计完一个子区间,就把开头的字符数量减一,这样就能确定每个子区间有哪些字符。

2,同时,最后一段子串必然以字符串的最后一个字符结尾,所以如果字符集合 T T T 不包含字符串的最后一个字符也是不合法的。

3,除了以上两种情况,如果 T T T 存在任意一个子集不合法,说明 T T T 包含了更多了结尾字符也存在不合法的情况,那么 T T T 必然也是不合法的,否则 T T T 是合法的,因此 f T = A N D ( f t ) ( t ∈ T ) f_T = AND(f_t)(t \in T) fT=AND(ft)(tT)

在大小与 T T T 差一的子集时,因为集合 T T T 子集的子集仍然是 T T T 的子集,所以把更小的子集的 A N D AND AND 和也计算进去了,所以 f T f_T fT 可以由大小差一的子集计算而来,即 f T = A N D ( f t ) ( t ∈ T , ∣ T ∣ − ∣ t ∣ = 1 ) f_T = AND(f_t)(t \in T,|T|-|t|=1) fT=AND(ft)(tT,Tt=1)

用二进制表示集合 T T T,从高到低第 i i i 位为 1 1 1 表示不含第 i i i 小字符,则 f T = A N D ( f t ) ( t = T − 2 i , 2 i ∈ T ) f_T = AND(f_t)(t = T-2^i,2^i \in T) fT=AND(ft)(t=T2i2iT)

从小到大计算 [ 0 , 2 c − 1 ] [0,2^c-1] [0,2c1] 的合法性,如果集合合法,则对应二进制串中 0 0 0 的数量表示该集合包含的字符,因此子串结尾字符的最少种类为所有合法集合中 0 0 0 的最少数量。

复杂度

空间复杂度 O ( 2 c − 1 ) O(2^c-1) O(2c1),时间复杂度 O ( n ∗ c + 2 c ∗ c ) O(n*c+2^c*c) O(nc+2cc)

代码实现

// Problem: D. Cases
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/D?locale=en
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N = 1 << 18;

int n, m, k, f[N];
string s;

void solve()
{
    cin >> n >> m >> k >> s;
    s = ' ' + s;
    for (int i = 0; i < (1 << m); i++) {
        f[i] = 1;
    }
    int cnt[26] = { 0 };
    for (int i = 1; i <= n; i++) {
        cnt[s[i] - 'A']++;
        // 增加新加入的字符数量
        if (i >= k) {
            int sta = 0;
            for (int j = 0; j < m; j++) {
                if (cnt[j])
                    sta |= (1 << j);
                // 存在对应字符
            }
            f[sta] = 0;
            cnt[s[i - k + 1] - 'A']--;
            // 删去子区间开头的字符
        }
    }
    int ans = m;
    for (int i = 0; i < (1 << m); i++) {
        for (int j = 0; j < m; j++) {
            if (((i >> j) & 1)) {
                f[i] &= f[i ^ (1 << j)];
                if (j == s[n] - 'A')
                    f[i] = 0;
                // 如果集合i不包含结尾字符 s[n] 也是不合法的
            }
        }
        if (f[i]) {
            int cur = 0;
            for (int j = 0; j < m; j++) {
                cur += ((i >> j) & 1) == 0;
            }
            ans = min(ans, cur);
        }
    }
    cout << ans << '\n';
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}
  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值