牛客周赛 Round 108

赛时F题差一个样例没过....

A. ICPC World Finals

题目描述

在大学生算法竞赛的社区里流传着这样一句话:"如果一个队的队长四级没过并且挂了科,那么他就 WF 了。"(也就是说如果一个队长没通过英语四级考试(CET4)并且存在必修课不及格的记录,则他就能晋级 ICPC World Finals,也就是世界总决赛,简称 WF。)
当然,这显然是一句玩笑话。通过夸张的描述体现出晋级 WF 需要超乎常人的努力,以至于可能会影响到很多别的学业。
但小苯身为队长仍然对此深信不疑,因此他给定你:他的四级成绩 sss,以及作为衡量指标的三门必修课成绩 s1,s2,s3,请你来判断一下,依照上述的传言,他能否晋级 WF。
注意:四级通过的分数线为:425 分,必修课及格的分数线为:60 分。

解题思路:按题目要求来写

#include<bits/stdc++.h>
using namespace std;
void solve(){
    int s,s1,s2,s3;
    cin>>s>>s1>>s2>>s3;
    if(s<425 && (s1<60||s2<60||s3<60)) { cout<<"YES"<<'\n'; return; } 
    else cout<<"NO"<<'\n';
}
int main(){
    int t=1; 
//     cin>>t;
    while(t--){
        solve();
    }
}

B. 小苯的数字排序

题目描述

小苯有 n 个数字 a1,a2,…,an​,他希望将这些数字按照以下规则排序:
1..​所有偶数排在所有奇数前面;
2.​在规则 1 的基础上,偶数与偶数之间、奇数与奇数之间,都按照数值从小到大的顺序排列。
请你帮他排出一个合理的顺序吧。

解题思路:先排序, 按要求输出偶数排在前面, 奇数排在后面

#include<bits/stdc++.h>
using namespace std;
void solve(){
   int n; cin>>n;
   vector<int> a(n);
   for(int i=0;i<n;i++) cin>>a[i];
   sort(a.begin(),a.end());
   for(int i=0;i<n;i++){
       if(a[i] & 1){
            continue;
       }else{
            cout<<a[i]<<" ";
       }
   }
   for(int i=0;i<n;i++){
       if(a[i] & 1){
            cout<<a[i]<<" ";
       }
   }
   cout<<'\n';
}
int main(){
    int t; 
    cin>>t;
    while(t--){
        solve();
    }
}

C.小苯的数字合并

题目描述

小苯有一个长度为 n 的数组 a1,a2,…,an​,他可以对 a 进行任意次“数字合并”操作,具体地,一次数字合并操作描述为:
选择一个下标 i(1≦i<∣a∣)i,将 ai​ 和 ai+1​ 合并为一个数字,结果为两个的和 ai+ai+1。合并后数组长度减 1,下标按新数组重新编号。
现在小苯可以进行任意次上述操作,他想知道他可以得到多少种本质不同结果数组,请你帮他数一数吧。换句话说,在可以进行任意次操作的情况下,所有可能得到的数组 a 有多少种本质不同的模样。由于答案可能很大,请将答案对 998 244 353 取模后输出。
小苯认为两个数组 a,b 本质不同,当且仅当以下两个条件至少满足其中之一:
,∙两个数组的长度不同;
,∙两个数组的长度相同,但存在至少一个 i(1≦i≦∣a∣),使得 ai≠bi。

解题思路:相邻数组元素合并后, 长度减1, n个元素, n-1个间隙, 每个间隙选/不选, 一共2^n-1种选法, 最后答案2^n-1 % 998 244 353

#include<bits/stdc++.h>
using namespace std;
const int M = 998244353;
using ll = long long;
ll fun(ll x,ll n){
    ll res=1;
    while(n){
        if(n & 1) res=res*x%M;
        x=x*x%M;
        n>>=1;
    }
    return res;
}
void solve(){
    int n; 
    cin>>n;
    vector<int> a(n);
    for(int i=0;i<n;i++) cin>>a[i];
    cout<<fun(2,n-1)<<'\n';
}
int main(){
    int t; 
    cin>>t;
    while(t--){
        solve();
    }
}

  D. 小苯的子序列权值

题目描述

小苯有一个长度为 n 的序列 a1,a2,…,an,他认为一个序列的权值为:序列中所有数字的按位与
现在小苯想知道所有的非空(显然一共 2^n−1 个)的子序列中,有多少个子序列的权值是偶数,请你帮他算一算吧。由于答案可能很大,请将答案对 998244353 取模后输出。
【名词解释】
按位与(Bitwise AND):对两个整数的二进制表示按位进行与运算。如果您需要更多位运算相关的知识,可以参考 OI-Wiki的相关章节
:从原序列中删除任意个(可以为零、可以为全部)元素得到的新序列。

解题思路:全部子序列的个数 - 奇数子序列的个数 = 偶数子序列的个数

权值是奇数的子序列中一定全是奇数

答案就是 2^n - 2^cnt

#include<bits/stdc++.h>
using namespace std;
const int M = 998244353;
using ll = long long;
ll fun(ll x,ll n){
    ll res=1;
    while(n){
        if(n & 1) res=res*x%M;
        x=x*x%M;
        n>>=1;
    }
    return res;
}
void solve(){
    int n; 
    cin>>n;
    vector<int> a(n);
    int cnt=0;
    for(int i=0;i<n;i++) { cin>>a[i];  if (a[i] & 1) { cnt++; } }
    ll b = fun(2,n); ll c= fun(2,cnt);
    cout << (b - c + M)%M << '\n';
}
int main(){
    int t; 
    cin>>t;
    while(t--){
        solve();
    }
}

E. 小苯的有趣数

题目描述

小苯发现了一些「有趣的」数字,即:数字本身是个完全平方数,且其各个数位之和也是个完全平方数!例如 2025 本身就是个完全平方数,同时其各个数位之和:2+0+2+5=9 也是个完全平方数,因此小苯认为 20252 就是个「有趣的」数字。
现在小苯有一个长度为 n 的序列 a1,a2,…,an​,他可以对 a 做任意次以下操作:
,∙选择两个不同的下标 i,j(1≦i,j≦n; i≠j),满足 ai≧2,随后将 ai​ 减去 1,aj加上 1。
他想知道,自己至多可以把 a 中多少个数字变成「有趣的」数字,请你帮他算一算吧。
【名词解释】
完全平方数:一个数如果可以表示为某个整数的平方,那么这个数就是完全平方数。前十个完全平方数是 0,1,4,9,16,25,36,49,64,81。

解题思路:

fun_1: 判断数字 x 是不是完全平方数

fun_2:判断各个数位之和是不是完全平方数

例如有长度为5的序列
1 4 6 8 9  tot_sum=28

tot_sum-5=23
23分配到5个位置, 能否形成5个有趣数
如果不能, 就4个位置为1, tot_sum 添加到第5个位置


特殊的当序列长度等于1时, 直接特判这个数是不是有趣数

所有满足条件的j*j, 将j*j-1存到 数组 b中
将数组b中的元素分配到原始的5个位置, 就会形成有趣数

上面我们把总和拆成 n 个数。
为了保证每个位置 至少是 1(因为不能放 0),先在每个位置放一个 1。
这样:
已经放了总和 n;
还差 sum - n = S 需要分配。
所以现在问题变成:
把 S 拆成若干份,每一份的大小必须是某个“有趣数的额外消耗”

什么是 “有趣数的额外消耗”?
如果我们把某个位置从 1 提升到 j²,那多消耗的就是 j² - 1
比如:

位置本来是 1,我想让它变成 4=2²,那这格子要多拿 4-1=3。
如果想让它变成 9=3²,就要多拿 9-1=8。
所以候选集合是:b={j^2-1|j^2-1<=S}

对态规划部分:
dp[i] = 凑出剩余和 i 时,最少需要用多少个有趣数的位置。
注意:这里的“有趣数”不是直接指 j² 本身,而是说 选择一个位置当作有趣数,需要消耗多少 S 值

举个例子:
我想让一个格子变成 9(即 3²),这意味着我从 S 里拿走 8(=9-1),同时这就贡献了 1 个有趣数。
如果我想让两个格子都变成 9,那我得从 S 里拿 16,同时这贡献了 2 个有趣数。

所以, 此时dp问题就转换成:
“物品重量” = j² - 1
“物品价值” = 1(即一个有趣数的数量)
“目标重量” = S
求最少价值能凑出目标重量。


为什么最后要看 dp[sum-n] <= n
dp[S] = 把 S 分配出去,最少需要多少个位置变成有趣数。
如果这个最小值 ≤ n,说明在 n 个位置里足够塞下这些有趣数,可以让所有位置都是有趣数 → 输出 n。
否则,说明即使用最优方式也需要超过 n 个有趣数,不可能每个位置都填好,只能退一步,答案是 n-1。
这样解释了为啥上面dp[i], 定义为最少需要.... , 如果定义最多就有可能超过n, 导致有趣数变为 n-1 

dp[i] 里的“最少需要的有趣数个数”,就是 在消耗额外值 i 时,最少需要多少个位置提升为有趣数。

dp其实就是完全背包:
若 i-x 可达(dp[i-x] != N),那么可以通过再加一个 x 来达到 i,此时所需个数为 dp[i-x] + 1。
dp[i] = min(dp[i], dp[i-x] + 1):取最少值

#include<bits/stdc++.h>
using namespace std;
const int M = 998244353;
const int N = 1e9+7;
using ll = long long;
ll fun(ll x,ll n){
    ll res=1;
    while(n){
        if(n & 1) res=res*x%M;
        x=x*x%M;
        n>>=1;
    }
    return res;
}
bool fun_1(int x) {
    if (x < 0) return false;
    int r = (int)floor(sqrt((double)x));
    while ((ll)r * r < x) ++r;
    while ((ll)r * r > x) --r;
    return (ll)r * r == x;                   
}
bool fun_2(int x){
    int sum=0;
    while(x){
        sum+=x%10;
        x/=10;
    }
    return fun_1(sum);
}
void solve(){
    int n; 
    cin>>n;
    vector<int> a(n);
    int sum = 0;
    for(int i=0;i<n;i++) { cin>>a[i]; sum+=a[i]; }
    if(n == 1) { if(fun_1(a[0]) && fun_2(a[0])) { cout<<1<<'\n'; }
    else cout<<0<<'\n';  return;     }
    if(sum-n<0) {
        cout<<0<<'\n';
        return;
    }
    if(sum-n == 0) { cout<<n<<'\n'; return; }
    vector<int> b;
    for(int j=0;(ll)j*j<=sum-n+1;j++){
        int x=j*j;
//         if(fun_1(x) && fun_2(x)){
//             if(x > 1) b.push_back(x-1);
//         }
        if(x==0) continue;
        if(fun_2(x)){
            if(x>1&&x-1<=sum-n) b.push_back(x-1);
        }
    }
    
    if(b.size()==0) { cout<<n-1<<'\n'; return; }
    vector<int> dp(sum-n+1,N);
    dp[0]=0;
    for(int x: b){
        for(int i=x;i<=sum-n;i++){
            if(dp[i-x]!=N){
                dp[i]=min(dp[i],dp[i-x]+1);
            }
        }
    }
    if(dp[sum-n]<=n) cout<<n<<'\n';
    else cout<<n-1<<'\n';
}
int main(){
    int t; 
    cin>>t;
    while(t--){
        solve();
    }
}

  F. AND VS MEX

题目描述

小苯有一个初始为空的可重数字集合 S 和一个长度为 n 的序列 a1,a2,…,an​。现在他可以进行任意次以下操作,给 S 中加入一些元素,具体的:
,∙他可以任选 a 中的任意个(也可以不选,此时 and⁡ 视为 0)数字,将这些数字的 and(按位与)加入集合 S。
他可以做任意次上述操作,请问 S 的 mex⁡ 最大可以达到多少。

【名词解释】
and:即按位与(Bitwise AND),指对两个整数的二进制表示按位进行与运算。如果您需要更多位运算相关的知识,可以参考 OI-Wiki的相关章节
mex:整数数组的 mex 定义为没有出现在数组中的最小非负整数。例如,mex⁡(1,2,3)=0、mex⁡(0,2,5)=1。

解题思路:

​​​​​

有一个长度为n的序列a, 你可以执行任意次以下操作,
从 a中任选数字进行&,操作,然后将&后的数字加入到集合S中
多次操作后
找出集合S中的最小非负整数

0~x-1能被凑出来, 那么答案就是x, 如果x 也能被凑数来, 那答案就是x+1

check(x) : x能否由a的某个子序列and凑出来
=> x的超集全and 起来 = x

eg:
    x: 11011
   超集: 11(0/1)11
x二进制中为1的地方超集也为1, x为0的地方超集可以0, 也可以为1
因此, x and 超集 = x

超级>=x , 越and越小, 因此选上所有超集, 如果选了非超集的数, 结果一定不等于x

高维前缀和 
fi: i的超集and的结果
只需枚举恰好多一个bit的超集

x: 00110110
多一个bit, 就类似于:
y: 10110110

#include <bits/stdc++.h>
using namespace std;
using ll =long long;
void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    int mx = -1;
    for(int i=0; i<n; i++){
        cin >> a[i];
        mx = max(mx, a[i]);
    }

    ll m = max<ll>(mx, n);
    int b = 0;
    while((1LL << b) <= m) b++;
    int c = 1 << b;
    int d = c - 1;

    vector<int> e(c, d);
    for(int x: a) e[x] &= x;
    for(int i=0; i<b; i++){
        for(int j=0; j<c; j++){
            if(((j >> i) & 1) == 0){
                e[j] &= e[j | (1 << i)];
            }
        }
    }

    for(int mex=1; mex<=n; mex++){
        if(e[mex] > mex){
            cout << mex << '\n';
            return;
        }
    }
    cout << n+1 << '\n';
}
int main(){
    int t;
    cin >> t;
    while(t--){
        solve();
    }
}

感谢大家的点赞和关注,你们的支持是我创作的动力!

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值