Codeforces Round 961 (Div. 2)[ABCD]

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

A. Diagonals

题意

给定一个 n ∗ n n*n nn 的矩阵, 定义对角线为 i + j i+j i+j 相同的值,其中 i i i为行号, j j j为列号 。

给你k个棋子要放在这么一个 n ∗ n n*n nn 的矩阵中, 问最少需要占据多少个对角线。

思路

对角线的长度为 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 。 我们按照对角线的长度从大到小按顺序填充棋子即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
    int n,k;
    cin>>n>>k;
    if(k == 0) {
        cout<<0<<endl;
        return ;
    }
    int cnt =1;
    k -= n;
    int t = n-1;
    int f = 0;
    while(k>0){
        k -= t;
        cnt++;
        if(f) t--;
        f ^= 1;
    }
    cout<<cnt<<endl;
}
signed main(){
    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

B. Bouquet

由于简单版本和困难版本差距不大,这边直接上hard vision

题意

有一个花店, 花店里有一些花,我们按照花所含有的花瓣数量来对花分类。你总共有x金币。

你可以花费k个金币购买一个拥有k个花瓣的花,并且你购买的花中,任意两朵花的花瓣数量相差不大于 1 1 1 。 问你最多能获得多少花瓣。

思路

我们使用map来记录每个花瓣的花有多少朵 , 显而易见, 若购买k花瓣的花,那么便只能再购买k+1花瓣的花 。所以我们枚举k为花店中每一种花的花瓣数量, 在每次枚举中,我们只考虑购买 k 和k+1的情况,最终在枚举的每种情况下取MAX即可。我们按照如下贪心策略来购买花,就可以保证买到的花瓣数量尽可能多。

  1. 尽可能多地购买k花瓣的花(直到没钱买或者把所有的花都买完了)
  2. 尽可能多地购买k+1花瓣的花(直到没钱买或者把所有的花都买完了)
  3. 尽可能多地将一些k花瓣的花换成k+1花瓣的花(每换一个花需要额外1金币, 并且要求有足够的k+1花瓣的花)

按照如上思路就可以得到“只够买k和k+1花瓣的花的情况下, 最多购买多少花瓣” ,将所有k的取值对应的答案取最大值就可以得到最终答案。

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
    int n,m;
    cin>>n>>m;
    map<int,int> mp;
    vector<int> a(n+1),b(n+1);
    rep(i,1,n){
        cin>>a[i];
    }
    rep(i,1,n){
        cin>>b[i];
    }
    rep(i,1,n) mp[a[i]] = b[i];
    int ans = 0;
    for(auto [x,y] : mp){
        if(y==0) continue;
        int sum = 0;
        int mm = m;
        int t1 = min(y,mm/x);
        mm -= t1 * x;
        int y2 = mp[x+1];
        int t2 = min(y2,mm/(x+1));
        mm -= t2 * (x+1);
        y2 -= t2;
        int t3 = min(mm,min(y2,t1));
        sum = t1 * x + t2 * (x+1) + t3;
        ans = max(ans,sum);
    }
    cout<<ans<<'\n';
}
signed main(){
    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

C. Squaring

题意

给一个长度为 n n n的数组 a 1 , a 2 , . . , a n a_1,a_2,..,a_n a1,a2,..,an , 每次操作你可以选择一个数 i i i, 令 a i = a i 2 a_i = a_i ^2 ai=ai2 ,请问最少需要多少次操作,能让数组a变为单调不减数列。若无论如何都无法实现,那么输出-1

思路

可以很容易想到,我们从左到右遍历数组,不断检查每一个数是否大于等于上一个数,若出现 a i < a i − 1 a_i < a_{i-1} ai<ai1 就对 a i a_i ai进行操作,直到满足 a i ≥ a i − 1 a_{i} \ge a_{i-1} aiai1

特判:只有当存在 a i = 1 , a i − 1 > 1 a_i = 1 ,a_{i-1} > 1 ai=1,ai1>1 时, 无论如何都无法令$ a_{i-1}\le a_i$ ,此时要输出-1

本题的难点在于在对一个数进行操作时,我们很容易就会发生超出longlong范围的情况,因此我们不能直接比较两个数的大小,而应该将他们作为 a p , b q a^p ,b^q ap,bq 的形式,直接比较二者的大小。


错解:

起初我很容易想到了一个”解法“, 即若判断 a p , b q a^p, b^q ap,bq 的大小关系, 可以比较 a 1 q a^{\frac{1}{q}} aq1 b 1 p b^{\frac{1}{p}} bp1 的大小关系, 于是便成功WA5了。。。


正解:

本题我们要用到每次操作都是平方的特性,即 a p , b q a^p ,b^q ap,bq中的 p , q p,q p,q 都是2的次幂, 假设 a i − 1 p > a i a_{i-1}^p > a_{i} ai1p>ai , 我们可以先令二者的幂相同,即设 c c c ,满足 c p = a i c^p = a_{i} cp=ai , 于是为了令 a i − 1 p ≤ a i q = ( c p ) q = ( c q ) p a^p_{i-1} \le a_i^q =(c^p)^q = (c^q)^p ai1paiq=(cp)q=(cq)p , 于是我们只需要找到一个 q q q ,使得恰有 c q ≥ a i − 1 c^q \ge a_{i-1} cqai1 即可, 也即 a i q p ≥ a i − 1 a_i ^ {\frac{q}{p}} \ge a_{i-1} aipqai1 , 又因为我们每次进行操作为乘方操作, 所以有 p , q p,q p,q 都是2的次幂 , 设 p = 2 x , q = 2 y p = 2^x,q = 2^y p=2x,q=2y , 那么 a i q p = a i 2 y − x a_i^{\frac{q}{p}} = a_i^{2^{y-x}} aipq=ai2yx , 此处的 y − x y-x yx 即为我们要对 a i a_i ai 进行的操作次数。

那么会出现下面两种情况:

  1. a i ≤ a i − 1 a_i \le a_{i-1} aiai1 ,那么我们不断对 a i a_i ai 进行乘二操作, 直到进行完 c n t cnt cnt 次操作后, a i ≥ a i − 1 a_i \ge a_{i-1} aiai1 ,此时对 a i a_i ai 进行的总次数即为 q = p + c n t q = p + cnt q=p+cnt
  2. a i > a i − 1 a_i > a_{i-1} ai>ai1 ,那么我们不断对 a i a_i ai 进行除以二操作, 直到进行完 c n t cnt cnt 次操作后,恰好 a i ≥ a i − 1 a_i \ge a_{i-1} aiai1 , 此时 a i a_i ai 进行的总次数即为 q = p − c n t q = p - cnt q=pcnt

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
#define endl '\n'
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
struct node {
    int x;
    int ope = 0;
};
void solve(){
    int n;
    cin>>n;
    vector<int> a(n+5);
    int ans = 0;
    vector<node> b(n+5);
    for(int i = 1;i<=n;i++){
        cin>>a[i];
    }
    for(int i = 2;i<=n;i++){
        if(a[i] < a[i-1] && a[i] == 1){
            cout<<-1<<'\n';
            return ;
        }
    }
    for(int i = 1;i<=n;i++){
        b[i] = {a[i],0};
    }
    for(int i = 2;i<=n;i++){
        int x1 = a[i-1];
        int q1 = b[i-1].ope;
        int x2 = a[i];
        int q2 = b[i].ope;
        if(x1 > x2){
            int xx = x2;
            int t = 0;
            while(xx < x1){
                xx *= xx;
                t++;
            }
            b[i].ope = b[i-1].ope + t;
        }else if(x1 == x2){
            b[i].ope = b[i-1].ope;
        }else{
            if(b[i-1].x == 1) {
                b[i].ope = 0;
                continue;
            }
            int t = 0;
            int xx = x1;
            while(xx < x2){
                xx *= xx;
                t++;
            }
            if(xx>x2) t--;
            b[i].ope = max(0LL,b[i-1].ope-t);
        }
        ans += b[i].ope;
    }
    cout<<ans<<'\n';
}
signed main(){
    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

D. Cases

题意

给你一个长度为 n n n的只含有前 c c c种字母的字符串 S S S,你可以将这个字符串切割为一些子串,并使得每一子串的长度都不大于 k k k , 定义切割后的字符串的价值为选取每一个子串的最后一个字符,将其放入集合set中,最终set的大小 , 即所有子串最后一个字符的种类数量。

问给定 n , c , k , S n,c,k,S n,c,k,S ,最小价值为多少。

思路

题意可以理解为, 我们尽可能少地选择一些字母, 要求相邻的被选择的字母之间距离不大于k。

也即: 任选一个长度为k的子串,其中必须包含至少一个我们所选择的字母。

我们判断一个长度为k的子串中有没有某一个字符,可以使用前缀和来判断,我们用 s u m [ i ] [ j ] sum[i][j] sum[i][j] 表示前i个字符中,有多少个字符j

if(sum[i+k][j] - sum[i][j]==0)

我们提取所有的长度为k的子串,将他们没出现过的字母状态进行二进制表示后存储起来,表示“只选择这些字母,会出现错误”。设对应的二进制码为msk,那么就令bad[msk] = 1 .

较容易得知,如果我们在选择一个组合之后,会发生错误,那么我们再从中去掉一些后,同样会出现错误。(如若选择AC 两种字母会出现相邻的被选择的字母之间距离大于k ,那么如果我们只选择A 或者C ,就一定也会导致相邻的被选择的字母之间距离大于k

例如样例:

6 3 2
ABCABC

我们提取出长度为2的子串后,得到AB, BC, CA, AB,BC , 取反后得到C ,A ,B ,C, A , 即如果选择其中一种组合或其子集(A或B或C或空) ,会出现错误。

在处理完bad数组后, 我们选出含有1最少的i,并且bad[i] == 0 , 于是 i i i中1的数量即为答案。

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
int sum[300005][20];
void solve(){
    int n,c,k;
    cin>>n>>c>>k;
    string str; 
    cin>>str;str = " " + str;
    for(int j = 0;j<c;j++) sum[0][j] = 0;
    for(int i = 1;i<=n;i++){
        for(int j = 0;j<c;j++){
            sum[i][j] = sum[i-1][j] + (str[i] == j + 'A');
        }
    }

    vector<bool> bad((1<<c));
    for(int i= 0;i<=n-k;i++){
        int msk = 0;    
        for(int j = 0;j<c;j++){
            if(sum[i+k][j] ==  sum[i][j] ){
                msk |= (1<<j);
            }
        }
        bad[msk] = 1;
    }
    bad[(1 << (str.back() - 'A'))^((1<<c)-1)] = 1;
    for(int i =(1<<c)-1;i>=0;i--){
        for(int j=0;j<c;j++){
            bad[i] = bad[i] | bad[i | (1<<j)];
        }  
    }
    int res = INF;
    for(int i = 0;i<(1<<c);i++){
        if(!bad[i]){
            int cnt = __builtin_popcount(i);
            res = min(res,cnt);
        }
    }
    cout<<res<<endl;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值