2022牛客寒假算法基础集训营(一)1全部题解

本文精选了算法竞赛中的经典题目,涵盖了DP动态规划、数学思维、分块算法等多种技巧,并提供了详细的题解思路及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


比赛链接


A 九小时九个人九扇门 dp

题目链接
题意:
一个数字的数字根是指:将该数字各数位上的数字相加得到一个新的数,直到得到的数字小于 10 10 10 为止.。设置小于 10 10 10 的数字,其数字根就为其本身。
k k k 个人能够打开门上数字为d的一扇数字门,当且仅当这 k k k 个人的腕表数字之和的数字根恰好为 d d d
( 1 < = n < = 1 e 5 , 1 < a i < = 1 e 9 ) (1<=n<=1e5,1<ai<=1e9) (1<=n<=1e5,1<ai<=1e9)

题解:
状态
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示考虑了前 i i i 个数,选择了一些数字使得数字根为 j j j 的方案数

转移方程
不加当前位使得数字根为 j j j 的方案数为上一位继承来
d p [ i ] [ j ] + = d p [ i − 1 ] [ j ] ; dp[i][j] += dp[i - 1][j]; dp[i][j]+=dp[i1][j];
加上当前位使得数字根为 f ( a [ i ] ∗ 10 + j ) f(a[i]*10+j) f(a[i]10+j) 的方案数为上一位继承来
d p [ i ] [ f ( a [ i ] ∗ 10 + j ) ] + = d p [ i − 1 ] [ j ] ; dp[i][f(a[i]*10+j)] += dp[i - 1][j]; dp[i][f(a[i]10+j)]+=dp[i1][j];
当前位的方案数+1
d p [ i ] [ f ( a [ i ] ) ] + + ; dp[i][f(a[i])]++; dp[i][f(a[i])]++;

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+100; 
const int M=998244353;
int t;
int a[N];
int b[N];
int f[10][N];
int fun(int n) {
	if(n<10) return n;
	while(n>=10) {
        int m=0;
        while(n) {
            m+=n%10;
            n/=10;
        }
        n=m;
	}
	return n;
}
signed main() {
	t=1;
	while(t--) {
		int n;
		cin>>n;
		for(int i=1; i<=n; i++) {
			cin>>a[i];
			a[i]=fun(a[i]);
			for(int j=0; j<=9; j++) {
				int tmp=fun(a[i]*10+j);
				f[j][i]+=f[j][i-1]%M;
                f[tmp][i]=(f[tmp][i]+f[j][i-1])%M;
			}
			f[a[i]][i]++;
		}
		for(int i=1; i<=9; i++) {
			cout<<f[i][n]%M<<" ";
		}
		cout<<endl;
	}
	return 0;
}

H 牛牛看云 思维

题目链接
题意:

n n n n n n
Σ Σ Σ Σ Σ Σ ∣ a i + a j − 1000 ∣ ∣ai+a j −1000∣ ai+aj1000
i i i=1 j j j= i i i
( 0 < a i < = 1000 , n < = 1000000 ) (0<ai<=1000, n<=1000000) (0<ai<=1000,n<=1000000)

题解:
因为这个 n n n 很打, a i ai ai很小
就从 a i ai ai 入手
记录 a i ai ai 的个数枚举 a i ai ai
直接计算答案
假如 a i = = a j ai==aj ai==aj ans=自己和自己+自己和别人 / 2 /2 /2 (因为 j j j 是从 i i i 开始的 所以 / 2 /2 /2
a i ! = a j ai!=aj ai!=aj ans= c n t [ a i ] ∗ c n t [ a j ] / 2 cnt[ai]*cnt[aj]/2 cnt[ai]cnt[aj]/2

vector<int> g[N];
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0); cout.tie(0);
    int n;cin>>n;
    for(int i=1; i<=n; i++) {
        int x; 
        cin>>x;
        g[x].push_back(i);
    }
    int ans=0;
    int a, b;
    for(int i=0; i<=1000; i++) {
        for(int j=i; j<=1000; j++) {
            int a=g[i].size();
            int b=g[j].size();
            if(i==j) {
                ans+=(a+a*(a-1)/2)*abs(i+i-1000);
            } else {
                ans+=a*b*abs(i+j-1000);
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

F 中位数切分 思维

题目链接
题意:
给定一个长为 n n n 的数组 a a a 和一个整数 m m m,求最多可以划分成多少段,使得每一段的中位数都大于等于 m m m ( 1 < a i , m < = 1 e 9 , 1 < = n < = 1 e 5 ) (1<ai, m<=1e9, 1<=n<=1e5) (1<ai,m<=1e9,1<=n<=1e5)
题解:
原数组大于等于m的记为1,记录 c n t 1 cnt1 cnt1
小于的记为-1,记录 c n t 2 cnt2 cnt2
当一段的 s u m > = 1 sum>=1 sum>=1 的时候这一段中位数就 > = m >=m >=m
然后就去拿 1 1 1 的去中和 − 1 -1 1, 最大的段数就是先把负数中和了使得 s u m = = 0 sum==0 sum==0 再加上一个 1 1 1,假如 c n t 1 − c n t 2 < = 0 cnt1-cnt2<=0 cnt1cnt2<=0则不存在,不然段数为 c n t 1 − c n t 2 cnt1-cnt2 cnt1cnt2个 。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
const double eps=1e-4;
int n, m;int a[N];
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0); cout.tie(0);
    int t;
    cin>>t;
    while(t--) {
         cin>>n>>m;
         int p=0;
         for(int i=1; i<=n; i++) {
            cin>>a[i];
            if(a[i]>=m) p++;
         }
         if(p-(n-p)>0) cout<<p-(n-p)<<endl;
         else cout<<-1<<endl;
    }
    return 0;
}

I B站与各唱各的 数学

题目链接
题意:
分子为欧拉函数
在这里插入图片描述
在这里插入图片描述

题解:
打表呜呜呜
最小值是
在这里插入图片描述
下一个数是210
就发现是素数的乘积 2 , 2 ∗ 3 , 2 ∗ 3 ∗ 5 2,2*3,2*3*5 2,23,235
因为素数乘积的数不是素数,那么他的因子也少
那么 H ( x ) H(x) H(x)就大
因为素数 x x x 的欧拉函数等于 x − 1 x-1 x1,那么最大值就是范围内最大的素数的 H ( x ) = ( x − 1 ) / x H(x)=(x-1)/x H(x)=(x1)/x
p s : ps: ps:牛客题解上说 1 e 9 1e9 1e9 以内最大的两个素数间隔是 282 282 282 就直接暴力找

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
bool vis[1000005];
int pri[1000005], cnt, n;
void get_prime(int n) {
    vis[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            pri[++cnt] = i;
            for (int j = 2 * i; j <= n; j += i) {
                vis[j] = 1;
            }
        }
    }
}
int xiao[N];
signed main() {
    get_prime(100005);
    ios_base::sync_with_stdio(0), cin.tie(0); cout.tie(0);
    xiao[0]=1;
    for(int i=1; i<=cnt; i++) {
         xiao[i]=xiao[i-1]*pri[i]; 
         if(xiao[i]>=1e9) break;
    }
    cnt=10;
    int t; cin>>t;
    while(t--) {
        int n;
        cin>>n;
        if(n==1) {
            cout<<-1<<endl;
            continue;
        }
        int sum=1;
        for(int i=cnt; i>=1; i--)  {
            if(xiao[i]<=n)  {
				cout<<xiao[i]<<" ";
                break;
            }
        }
        for(int i=n; i>=1; i--) {
                int q=0;
                for (int j=2; j<=sqrt(i); j++) {
                    if (i%j==0) {
                        q=1;
                        break;
                    }
                }
                if(!q) {
                    cout<<i<<endl;
                    break;
                }
        }
    }
    return 0;
}

D 牛牛做数论 数学

题目链接

题意:
n n n 位人在翻唱一首共 m m m 句的歌曲,人不交流。一句歌词被所有人唱或者没被人唱这句歌词无效,让成功唱出的句子数尽可能多,求期望唱成功的句子数量对1e9 +7取模的结果。
( 1 < = t < = 1 e 4 , 1 < = n , m < = 1 e 9 , ) (1<=t<=1e4,1<=n, m<=1e9,) (1<=t<=1e4,1<=n,m<=1e9,)

题解:
m m m 句歌词相互独立
设唱的概率为 p i pi pi 不唱的概率为 ( 1 − p i ) (1-pi) (1pi)
那么答案就是失败的概率就是 ( p 1 ∗ p 2 ∗ p 3 ∗ . . . ∗ p n ) (p1*p2*p3*...*pn) (p1p2p3...pn)+ ( 1 − p 1 ) ∗ ( 1 − p 2 ) ∗ . . . ∗ ( 1 − p n ) (1-p1)*(1-p2)*...*(1-pn) (1p1)(1p2)...(1pn)
那么成功的概率就是 1 − ( p 1 ∗ p 2 ∗ p 3 ∗ . . . ∗ p n ) 1-(p1*p2*p3*...*pn) 1(p1p2p3...pn)+ ( 1 − p 1 ) ∗ ( 1 − p 2 ) ∗ . . . ∗ ( 1 − p n ) (1-p1)*(1-p2)*...*(1-pn) (1p1)(1p2)...(1pn)
最大化成功就是最小化石板就要使得pi都为 1 / 2 1/2 1/2 其实是猜的不过可以验算几个
那么答案就等于 m ∗ ( 1 − ( 1 / 2 ) n ∗ 2 ) m*(1-(1/2)^n*2) m(1(1/2)n2)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
const int N=1e5+5;
const int M=1e9+7;  
ll ksm(ll a,ll p){ll res=1;while(p){if(p&1){res=res*a%M;}a=a*a%M;p>>=1;}return res;}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0); cout.tie(0);
    int t;cin>>t;
    while(t--) {
        int n, m;
        cin>>n>>m;
        cout<<(m%M*(ksm(2, n-1)%M-1+M)%M*ksm(ksm(2, n-1)%M, M-2)%M)%M<<endl;
    }
    return 0;
}

B 炸鸡块君与FIFA22 倍增/分块/线段树【补】

题目链接
【补】
题意:
打游戏胜利将使得分加一、失败使分减一、平局使分不变。若你当前的排位分是 3 3 3 的整倍数(包括0倍),则若下一局游戏失败,你的排位分将不变(而不是减一)
给定一个游戏结果字符串和若干次询问,你需要回答这些询问。
( 1 < = l , r < = n , q < = 2 e 5 , 0 < = s < = 1 e 9 ) (1<=l, r<=n,q<=2e5,0<=s<=1e9) (1<=l,r<=nq<=2e5,0<=s<=1e9)

题解:
n , q n,q n,q 很大就想到要预处理
每次询问每段的答案发现只和首位的初始值%3的值有有关
那么预处理从每一位开始以初始值为 0 , 1 , 2 0, 1, 2 0,1,2开始向后进行操作,但是不能 n ∗ n n*n nn
所以就想到暴力分块
假如在同一个分块里就直接暴力模拟
不在的话
就在一整块和另一块以%3的值结果来转移

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
char str[N];
int d[N][5];//dij代表第i块以j为首值处理后的结果
int bl[N], l[N], r[N];
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0); cout.tie(0);
    int n, q;
    cin>>n>>q;
    cin>>str+1;
    int B=sqrt(n);
    for(int i=1; i<=n; i++) bl[i]=(i+B-1)/B;
    for(int i=1; i<=bl[n]; i++) l[i]=r[i-1]+1, r[i]=r[i-1]+B;
    r[bl[n]]=n;
    for(int i=1; i<=bl[n]; i++) {
        d[i][0]=d[i][1]=d[i][2]=0;
        for(int j=l[i]; j<=r[i]; j++) {
            if(str[j]=='W') {
                d[i][0]++, d[i][1]++, d[i][2]++;
            }else if(str[j]=='L') {
                if(d[i][0]%3) d[i][0]--;
                if((d[i][1]+1)%3) d[i][1]--;
                if((d[i][2]+2)%3) d[i][2]--;
            }
        }
    }
    while(q--) {
        int le, re, s;
        cin>>le>>re>>s;
        if(bl[le]==bl[re]) {
            for(int i=le; i<=re; i++) {
                if(str[i]=='W') {
                    s++;
                }else if(str[i]=='L') {
                    if(s%3) s--; 
                }
            }
            cout<<s<<endl;
            continue;
        }
        for(int i=le; i<=r[bl[le]]; i++) {
            if(str[i]=='W') {
                s++;
            }else if(str[i]=='L') {
                if(s%3) s--; 
            }
        }
        for(int i=bl[le]+1; i<bl[re]; i++) {
            s=s+d[i][(s+3)%3];
        }
        for(int i=l[bl[re]]; i<=re; i++) {
                if(str[i]=='W') s++;
                else if(str[i]=='L') {
                    if(s%3) s --;
                }
        }
        cout<<s<<endl;
    }
    return 0;
}

待补

K 冒险公社 dp【补】

题目链接
【补】
qwq
发现,第 i i i 个预测和 [ 1 , i − 3 ] [1,i−3] [1,i3] 的所有岛都没有关系,明显符合 dp 的无后效性

d p [ i ] [ i 1 ] [ i 2 ] [ i 3 ] dp[i][i1][i2][i3] dp[i][i1][i2][i3]表示已经走了 i i i 座山绿岛的最大数量, i 1 , i 2 , i 3 i1, i2, i3 i1,i2,i3表示 i , i − 1 , i − 2 i,i-1, i-2 i,i1,i2的颜色
d p [ i ] [ i 1 ] [ i 2 ] [ i 3 ] dp[i][i1][i2][i3] dp[i][i1][i2][i3]是从合法状态的 d p [ i − 1 ] [ i 2 ] [ i 3 ] [ i 4 ] dp[i−1][i2][i3][i4] dp[i1][i2][i3][i4]转移过来

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N=1e5+5; 
int dp[N][5][5][5];
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0); cout.tie(0);
    int n; cin>>n;
    string s; cin>>s; s='.'+s;
    for(int i=3; i<=n; i++) for(int j=0; j<3; j++)for(int k=0; k<3; k++) for(int l=0; l<3; l++) dp[i][j][k][l]=-1;
    for(int j=0; j<3; j++)for(int k=0; k<3; k++) for(int l=0; l<3; l++) {
        dp[2][j][k][l]=0;
        //if(j==1) dp[2][j][k][l]++;
        if(j==1) dp[2][j][k][l]++;
        if(k==1) dp[2][j][k][l]++;
    }
    int ans=-1;
    for(int i=3; i<=n; i++) {
        for(int l=0; l<3; l++) {
            for(int j=0; j<3; j++) {
                for(int k=0; k<3; k++) {
                    for(int l1=0; l1<3; l1++) {
                        if(dp[i-1][j][k][l1]==-1) continue;
                        int R=0, G=0;
                        R=(l==0)+(j==0)+(k==0);
                        G=(l==1)+(j==1)+(k==1);
                        //cout<<R<<" "<<G<<endl;
                        if(s[i]=='R'&&G<R) dp[i][l][j][k]=max(dp[i][l][j][k], dp[i-1][j][k][l1]+(l==1));
                        if(s[i]=='G'&&G>R) dp[i][l][j][k]=max(dp[i][l][j][k], dp[i-1][j][k][l1]+(l==1));
                        if(s[i]=='B'&&G==R) dp[i][l][j][k]=max(dp[i][l][j][k], dp[i-1][j][k][l1]+(l==1));
                    }
                }
            }
        }
    }
    for(int i=0; i<3; i++)for(int j=0; j<3; j++) for(int k=0; k<3; k++)
        ans=max(ans, dp[n][i][j][k]);
    cout<<ans<<endl;
    return 0;
}

G ACM is all you need 【待补】

题目链接


总结

Qwq

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值