2026牛客寒假算法基础集训营1

A(逆元 模拟 状态压缩 概率论 逆元)

大致题意:

有八个独立的数位显示器,每个显示器的每个二极管被点亮的概率为pi ,管与管之间互相独立,显示器 之间也相互独立,求分别显示出两个四位合法数字,且数字之和等于输入的常数 的概率。

现在请你计算出如下事件的概率(需全部满足):

• 最终所有显示器均有灯管被点亮(也就是说显示器的灯管不能全灭)。

• 最终所有显示器显示的结果均为合法数字。

• 第一排的显示器前后拼接形成的十进制数记作A,第二排的显示器前后拼接形成的十进制数记作 B的话,满足:A+B=C。

(需要注意的是:我们认为A和B都可以存在前导0。)

也就是说 我们要让所有显示器亮出来一个合法数字并且前后数字之和为c

首先 显示器之间完全独立 我们可以算出每个显示器表示0-9的概率 这样的话我们就可以用独立的概率乘积算出某个数字的概率 这样我们就可以枚举1-2026 的所有数字 算出概率 然后算出所有组合的概率和

接下来我们要解决如何算出表示0-9的概率 这里需要用到状态压缩的技巧 我们在二进制中 将需要点亮的灯管定为1 不需要的定为0 我们可以写一张表格 把0-9的二进制状态存起来

cpp 复制代码
void init() {
S[0] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5) | (1 << 6);
S[1] = (1 << 2) | (1 << 5);
S[2] = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 6);
S[3] = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5) | (1 << 6);
S[4] = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5);
S[5] = (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 6);
S[6] = (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
S[7] = (1 << 0) | (1 << 2) | (1 << 5);
S[8] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | 
(1 << 6);
S[9] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5) | (1 << 6);
}

然后枚举A的概率*C-A的概率即可

整个过程需要取模 除法也要用乘法逆元代替

【分数取模教程】

以下文中的mod表示取模,也就是大家平时写的%符号。 我们以任意分数:a/b 举例。

直接给出结论:根据费马小定理,在模数m为质数,且b不是m的倍数的情况下有: a/b mod m=a×(b^m−2) mod m

也就是说在mod m的意义下,1/b =b^ (m−2) mod m。 例如:在mod998244353的情况下,2 3 = 2×3998244351(mod998244353),这是因为 998244353 是一个质 数,且3不是998244353的倍数。 上式中,b ^m−2 mod m实际上就是我们常说的"乘法逆元",即把"a除以b"转化为"a乘上b的逆元",这 样一来结果算下来是正确的,同时我们将分数(也就是小数)域下的运算转为了整数域下的运算,这样一 来就避免了小数可能产生的精度问题,同时不影响答案的正确性。 证明需要大量计算,这里不再展开,我们只需要使用结论即可

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int c;
int p[8];
int S[10];

void init() {
    S[0] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5) | (1 << 6);
    S[1] = (1 << 2) | (1 << 5);
    S[2] = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 6);
    S[3] = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5) | (1 << 6);
    S[4] = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5);
    S[5] = (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 6);
    S[6] = (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
    S[7] = (1 << 0) | (1 << 2) | (1 << 5);
    S[8] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
    S[9] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5) | (1 << 6);
}

int ksm(int a,int b,int mod){
    int ans=1;
    a = a % mod; 
    for(;b;b>>=1){
        if(b&1)ans=(ans*a)%mod; 
        a=(a*a)%mod;
    }
    return ans;
}

int inv100=ksm(100,mod-2,mod);

void solve(){
    cin>>c;
    for(int i=0;i<7;i++){
        cin>>p[i];
        p[i]=(p[i]*inv100)%mod;
    }

    vector<int>digit(10,1);
    for(int i=0;i<10;i++){
        for(int j=0;j<7;j++){
            if(S[i]>>j&1){
                digit[i] = (digit[i] * p[j]) % mod;
            }else{
                int not_p = (1 - p[j] + mod) % mod;
                digit[i] = (digit[i] * not_p) % mod;
            }
        }
    }

    auto calc = [&](int x)->int{
        if(x==0){
            return ((digit[0] * digit[0] % mod) * digit[0] % mod) * digit[0] % mod;
        }else {
            int ans=1,len=0;
            int tmp = x;
            while(tmp>0){
                ans = (ans * digit[tmp % 10]) % mod;
                len++;
                tmp /= 10;
            }
            for(int i=0;i<4-len;i++){
                ans=(ans*digit[0])%mod;
            }
            return ans;
        }
    };

    int ans=0;
    for(int a=0;a<=c;a++){
        int b=c-a;
        ans = (ans + calc(a) * calc(b) % mod) % mod;
    }
    cout<<ans<<'\n';
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    init();
    int t;cin>>t;
    while(t--)solve();
    return 0;
}

B(贪心、思维、数学)

小苯正在和小红玩卡牌游戏。游戏中有2×n张牌,每张牌上都有一个数字,所有牌的数字恰好构成 了一个长度为2×n的排列。 游戏最初时,2×n张卡牌被恰好平均分成了两组n张牌,并分别发给了两人,小苯第i张牌上的数 字是ai,而小红第i张牌上的数字是bi,具体的游戏过程如下: • 如果两人之中有一人已经没有牌了,则游戏结束。 • 两人取出自己的牌堆里的第一张牌(编号为1)并比较大小,对应数字大的那一方得一分,同时弃 掉这张更大的牌;而数字小的一方则没有任何变化。(即既不弃牌,也不得分。) 而现在小苯希望自己的得分尽可能多,为此他在游戏开始前可以任意地重新排列自己的牌,以得到更 高的游戏分数。

只要前者手中大于后者最小值的牌 都可以得分 使得最后为最大分数 那么也就是大于后者的牌数在前方随意排列 小于后者最小值的牌在后方随意排列 两个组合数相乘即可

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
const int mod=998244353;
int a[N],n,b[N];
long long fact[N];
void preprocess() {
    fact[0] = 1;
    for(int i=1;i<=N;++i) {
        fact[i]=fact[i-1]*i%mod;
    }
}
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    sort(b+1,b+1+n);
    int k=0;
    for(int i=1;i<=n;i++){
        if(a[i]>b[1])k++;
    }
    long long ans=fact[k]*fact[n-k]%mod;
    cout<<ans<<'\n';
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    preprocess();
    int t;cin>>t;
    while(t--)solve();
    return 0;
}

C签到

D(二分 贪心)

题意: 给定一个序列,其中有白色数组也有黑色数字,再给定参数 表示一开始可以选择 个白数字染红,接 下来每秒都会发生:所有红色数字都会把其右侧 个数字里的白色数字染红。 求:以最优策略染红最初的 个白色数字的话,所有白色数字都会被染红的最短时间。 (注意:黑色数字不会、也无需被染红。)

首先 答案根据时间有有明显的单调性 可以二分答案 那么我们可以直接模拟染色 从左往右贪心染色 判断是否存在染色点不超过k个的情况下 可以把整个序列染色 可以用check函数进行实现

维护ans 与time 表示已经染色了ans个 且最右端的时间为time 我们要贪心的使得ans尽可能小 ans<=k 就是合法的

每次覆盖的最远位置 可能会出现位于前面的方块覆盖的更远 所以可以维护一个前缀max维护当前染色的最远位置

模拟过程中要注意跳过黑色方块

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int a[N];
int pre[N];
void solve(){
    int n,k;cin>>n>>k;
    for(int i=0;i<n;i++){    
        cin>>a[i];
        int now=0;
        if(a[i]){
            now=min(i+a[i]+1,n);
        }
        pre[i+1]=max(now,pre[i]);
    }
    auto check=[&](int x){
        int cur=0;
        while(cur<n&&!a[cur])cur++;
        for(int i=0;i<k&&cur<n;i++){
            cur++;
            for(int j=0;j<x&&pre[cur]>cur;j++){
                cur=pre[cur];
            }
            while(cur<n&&!a[cur])cur++;
        }
        return cur==n;
    };
    int lo=0,hi=n;
    while(lo<hi){
        int x=(lo+hi)/2;
        if(check(x)){
            hi=x;
        }else lo=x+1;
    }
    if(lo==n){
        lo=-1;
    }
    cout<<lo<<'\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

E(枚举 贪心)

题意: 有 个小方块排成一排,其中第 个小方块上的数字是 ,另外还有个万能方块上面数字是 。可以任 意次把万能方块从方块序列的最左侧插入,其余方块后移,同时最后一个方块变成新的万能方块。 最大化:第一个方块上的数字 + 万能方块上的数字。

首先 可以维护一个双端队列进行模拟这个过程 找出答案

其次 我们可以发现万能方块和第一个数字是一种相邻关系 我们只需要遍历所有的相邻数字 找到最大的和即可

G(按位贪心)

多测给定区间 ,定义 为把数字 的十进制翻转后去除前导 的值。 求区间中所有数字 的 最大值

首先 位数不同的时候 我们要尽量选择位数高的 除了r=100000 (10的幂次)这种特殊情况 代码处理上 比如一个9988 和 10025 我们就可以将l看作10000 r仍然为10025 不变

其次 位数相同的时候 对于公共前缀 无论如何都无法改变 如12455与12499 前缀124 是无法改变的 照抄即可

剩下的位数我们按位贪心 第一个不同的位数 r上面这一位减1 剩下的位数全填9 就可以得到最大数

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve(){
    string L,R;
    cin>>L>>R;
    int nl=L.size(),nr=R.size();
    int l = stoll(L), r = stoll(R);
    string t="1";
    for(int i=0;i<nr-1;i++){
        t+='0';
    }
    if(R==t){
        if(L==R)cout<<1<<'\n';
        else {
            cout<<r-1<<'\n';
        }
        return;
    }
    if(nl<nr){
        L=t;
        L.back()+=1;
        assert(L.size() == R.size());
    }
    string ans;
    int k=-1;
    for(int i=0;i<nr;i++){
        if(L[i]!=R[i]){
            k=i;break;
        }
    }
    if(k==-1){
        ans=L;
        while(ans.size()>1&&ans.back()=='0'){
            ans.pop_back();
        }
        reverse(ans.begin(),ans.end());
    }else {
        bool flag=1;
        for(int i=k+1;i<nr;i++){
            ans+='9';
            flag&=(R[i]=='9');//后面本身就全是 9  不用高位减一
        }
        ans+=(R[k]-!flag);
        for(int i=k-1;i>=0;i--){
            ans+=L[i];
        }
    }
    cout<<ans<<'\n';
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;cin>>t;
    while(t--)solve();    
    return 0;
}

H(位or运算、前缀和优化DP、计数)

题意: 给定一个序列 ,表示运算式:,问有多少种把 替换成 (位运算按位或)的 方式,使得不改变运算式的值。(不替换也是一种方案,且特别的:本题中认为 运算优先级大于 。)

dp

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int mod=998244353;
void solve(){
    int n;
    cin>>n;
    vector<int>a(n+1);
    int lst=0;
    vector<int>pre(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        pre[i]=lst;
        if(a[i]>0)lst=i;
    }
    vector<int>dp(n+2);
    vector<int>s(n+2);
    dp[1]=1;
    s[1]=1;
    for(int i=1;i<=n;i++){
        int j=i;int val=0;
        while(j>0&&(val&a[j])==0){
            val|=a[j];
            j=pre[j];
        }
        dp[i+1]=(s[i]-s[j]+mod)%mod;
        s[i+1]=(s[i]+dp[i+1])%mod;
    }
    cout<<dp[n+1]<<'\n';
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;cin>>t;
    while(t--)solve();    
    return 0;
}

I(位运算 构造 贪心)

题意: 多测给定区间[l,r] ,可以任意次操作从区间中选若干个数字,把 数字的and 加入集合S ,求S 的 mex最大 值。

1.l=0的时候 显然是r+1;

  1. 我们定义highbit为数字的最高一位 那么当了l,r两个数字的最高位相同的时候 and的结果都一定有最高位 那么最后的答案一定是0

3.当最高位相差两位的时候 如0010 1001

l到r之间的所有数字显然都可以选择一个数字构造出来 对于0到l的部分 区间中一定有一个使得两个数字相差一位的全为1 的数字 111 和一个三位的开始100 我们可以用100到111 之间的所有数字对0011进行and操作 就可以得到0到l的所有数字

4.当最高位只差1 如0101 1011 显然 我们要构造0到l的数字 类比情况3 我们可以找到一个位数交界处 111 那么我们可以用1000 到1011 构造出000到011 的所有数字 也就是r去掉最高位的数字 其次 l可以构造出比自己小的数字 比如l=1101100 当r比l多一位的时候 区间内一定存在1101111 这个数字 以及[1110000,1111111]这段数字 我们用前者分别与后者and操作就可以得到[1100000 ,l]的数字 也就是使得第一个0后面全为0 的数字 我们只需要判断这两个区间有没有交集 就可以判断最后的结果

代码实现如下

cpp 复制代码
    #include <bits/stdc++.h>
    using namespace std;
    void solve(){
        int l,r;
        cin>>l>>r;
        auto highbit=[&](int x){
            for(int i=32;i>=0;i--){
                if(x>>i&1){
                    return i;
                }
            }
            return -1;
        };
        int b1=highbit(l),b2=highbit(r);
        if(b1==-1){
            cout<<r+1<<'\n';
        }   
        else if (b1==b2){
            cout<<0<<'\n';
        }
        else if(b2>b1+1){
            cout<<r+1<<'\n';
        }
        else {
            int ans=r-(1LL<<b2)+1;
            int L=0;
            for(int i=b1;i>=0;i--){
                if((l>>i&1)==0)break;
                else L|=(1<<i);
            }
            if(L<=ans){
                ans=r+1;
            }
            cout<<ans<<'\n';
        }
        

        
    }
    int main() {
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        int t;cin>>t;
        while(t--)solve();    
        return 0;
    }

K(签到 构造)

题意: 构造字典序最小的,长度为 的数组满足: 1. 所有数字均为正整数 2. 所有数字互不相同 3. 所有数字的和等于所有数字的乘

随便尝试几个 然后发现只有n=1 n=3 符合

L(签到)

相关推荐
野犬寒鸦1 小时前
Java8 ConcurrentHashMap 深度解析(底层数据结构详解及方法执行流程)
java·开发语言·数据库·后端·学习·算法·哈希算法
m0_531237171 小时前
C语言-函数递归练习
算法
回敲代码的猴子1 小时前
2月18日打卡
算法
追随者永远是胜利者1 小时前
(LeetCode-Hot100)647. 回文子串
java·算法·leetcode·职场和发展·go
宇木灵2 小时前
C语言基础-六、指针
c语言·开发语言·学习·算法
苦藤新鸡2 小时前
64 搜索平移递增数组中的元素
数据结构·算法
Vic101012 小时前
链表算法三道
java·数据结构·算法·链表
再难也得平2 小时前
[LeetCode刷题]128.最长连续序列(从零开始的java题解)
java·算法·leetcode
xiaoye-duck2 小时前
《算法题讲解指南:优选算法-双指针》--05有效三角形的个数,06查找总价值为目标值的两个商品
c++·算法