蓝桥杯2019年c++b组国赛题目及题解

填空题目来源来自于:https://blog.csdn.net/l503301397/article/details/90697079
大题来源于:ACwing https://www.acwing.com/problem/search/2/?csrfmiddlewaretoken=FZ3nfW3ve6OUKD4s75NP4EmknhbU2HMXthYBpJjyLLuNijuhPQDekbRmGr8Sc38s&search_content=%E8%93%9D%E6%A1%A5%E6%9D%AF
不一定都对,欢迎大佬们指出错误

A

1、2019 2、2019^2, X^2, Y^2构成等差数列
满足条件的X和Y可能有多种情况,请给出X+Y的值,并且令X+Y尽可能的小。

答案:7020
分析:暴力
代码:

#include
#include
#include 
using namespace std;
typedef long long ll;
int main ()
{
     
    //freopen("D:\\input.txt", "r", stdin);
    //freopen("D:\\output.txt", "w", stdout);
	cin.tie(0),ios::sync_with_stdio(false);
	for(ll i=2020;;i++){
     
        ll cha=i*i-2019*2019;
        ll j=i*i+cha;
        if(sqrt(j)*sqrt(j)==j){
     
            cout<<i+sqrt(j)<<endl;
            break;
        }
	}
	return 0;
}

B

2019可以被分解成若干个两两不同的素数,请问不同的分解方案有多少种?
注意:分解方案不考虑顺序,如2+2017=2019和2017+2=2019属于同一种方案。
答案:55965365465060

分析:题目中说的若干个,那么这就是一个01背包问题,直接用dp。
dp[i][j]表示前i个素数和为j时的方法数。对于第i个素数可以选或不选,每个那么状态转换方程就是:不选 dp[i][j] = dp[i-1][j];选:dp[i][j] += dp[i-1][j-a[i]](j >= a[i])。

代码:

#include
#include
using namespace std;
const int maxn=5e5+7;
typedef long long ll;
int vis[maxn];
int a[maxn];
ll dp[2020][2020]={
     1};
int isprime(int x){
     
    for(int i=2;i<=sqrt(x);i++){
     
        if(x%i==0)
            return 0;
    }
    return 1;
}
int main ()
{
     
    //freopen("D:\\input.txt", "r", stdin);
    //freopen("D:\\output.txt", "w", stdout);
	cin.tie(0),ios::sync_with_stdio(false);
	int cnt=0;5
	int n;
	cin>>n;
	for(int i = 2; i <= 2017 ;i++) {
     
        if(isprime(i)){
     
            vis[i] = 1;   
            a[++cnt] = i;
        }
    }
    for(int i = 1; i <= cnt ;i++) {
     
        for(int j = 0; j <= n ;j++){
     
            dp[i][j] =  dp[i-1][j];
            if( j >= a[i]) {
     
                dp[i][j] += dp[i-1][j-a[i]];
            }

        }
    }
    cout<<dp[cnt][n]<<endl;
	return 0;
}

C

7×7方格,分成两部分,每部分连通,右半部分翻转旋转拼接之后也是7×7,
有多少种分割方法
蓝桥杯2019年c++b组国赛题目及题解_第1张图片
答案:45406
分析:评论区有大佬指出了错误,在每一个点都可以向四个方向减,那么这也就是是一个搜索的问题。可以看出要让右边旋转可以仍然和左边拼成正方形,那么就需要剪出来右边和左边都是轴对称的。
既然剪出来的是对称的们就可以只考虑左半边怎么选择减的路径,答案应该是有多少种路径可以减到对角线上。
例如从(0,0)开始剪如图可见,有7处需要选择剪得得方向。开始剪的点是(0,0)。最后还要减去从起点出发一直向下或向左剪是一条直线的情况,答案就是45408-2=45406。
蓝桥杯2019年c++b组国赛题目及题解_第2张图片

#include 
using namespace std;
int vis[10][10];
int dir[4][2] = {
     1,0,-1,0,0,1,0,-1};
int ans=0;
void dfs(int x,int y)
{
     
    if( x + y == 7 ) {
     
        ans++;
        return ;
    }
    for(int i = 0 ;i < 4; i++) {
     
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if( xx+yy > 7||xx<0||xx>7 || yy<0 ||yy>7||vis[xx][yy])
            continue;
        vis[xx][yy] = 1;
        dfs(xx,yy);
        vis[xx][yy] = 0;
    }
}

int main()
{
     
    vis[0][0]  = 1;
    dfs(0,0);
    vis[0][0] = 0;
    cout<< ans<<endl;
    return 0;
}

D

有一个7X7的方格。方格左上角顶点坐标为(0,0),右下角坐标为(7,7)。
求满足下列条件的路径条数:
1、起点和终点都是(0,0)
2、路径不自交
3、路径长度不大于12
4、对于每一个顶点,有上下左右四个方向可以走,但是不能越界。
例如,图中路线,左上角顶点(0,0),路线长度为10

蓝桥杯2019年c++b组国赛题目及题解_第3张图片
答案:206
分析:dfs,路径一定大于2,剪枝路径>12的,以及x和y坐标和大于6时,一定不满足,结束搜索。
代码:

#include
#include
#include 
using namespace std;
typedef long long ll;
int vis[10][10]={
     0};
int dir[4][2]={
     1,0,-1,0,0,1,0,-1};
int ans = 0;
int check(int x,int y){
     
    if(0 <= x && x <= 7 && y >= 0 && y <= 7)
        return 1;
    else
        return 0;
}

void dfs(int x,int y,int cnt)
{
     
    if(x+y > 7 || cnt > 12)
        return;
    for(int i = 0; i <4 ;i++){
     
        int nx = x+dir[i][0];
        int ny = y+dir[i][1];
        if(nx == 0&&ny == 0&& cnt+1 >2&&cnt+1 <= 12)
            ans++;
        if(vis[nx][ny] == 1||check(nx,ny) == 0)
            continue;
        vis[nx][ny] = 1;
        dfs(nx,ny,cnt+1);
        vis[nx][ny] = 0;
    }
    return ;
}

int main ()
{
     
    //freopen("D:\\input.txt", "r", stdin);
    //freopen("D:\\output.txt", "w", stdout);
	cin.tie(0),ios::sync_with_stdio(false);
	vis[0][0]=1;
	dfs(0,0,0);
	cout<<ans<<endl;
	return 0;
}

E

有1个约数的最小数为1(1),有两个约数的最小数为2(1,2)……
有n个约数的最小数为Sn
S1=1 (1)
S2=2 (1 2)
S3=4 (1 2 4)
S4=6 (1 2 3 6)
求S100
答案:45360
分析:反正是个填空题,直接暴力就完了…

#include
#include
#include 
using namespace std;
typedef long long ll;
set<int>vis;
int slove(int x){
     
    vis.clear();
    int cnt =0;
    for(int i = 1; i <= x/2 ;i++) {
     
        if( x%i != 0)
            continue;
        if(vis.count(i) == 0) {
     
            cnt++;
            vis.insert(i);
        }
        if(vis.count(x/i) == 0) {
     
            cnt++;
            vis.insert(x/i);
        }
    }
    return cnt;
}

int main ()
{
     
    //freopen("D:\\input.txt", "r", stdin);
    //freopen("D:\\output.txt", "w", stdout);
	cin.tie(0),ios::sync_with_stdio(false);
	int ans=0;
	for(int i = 100; ;i++) {
     
            cout<<i<<" "<<slove(i)<<endl;;
        if(slove(i) == 100) {
     
            cout<<i<<endl;
            break;
        }
    }
    system("pause");
	return 0;
}

F最优包含

题目链接:https://www.acwing.com/problem/content/2555/
我们称一个字符串 S 包含字符串 T 是指 T 是 S 的一个子序列,即可以从字符串 S 中抽出若干个字符,它们按原来的顺序组合成一个新的字符串与 T 完全一样。

给定两个字符串 S 和 T,请问最少修改 S 中的多少个字符,能使 S 包含 T?

输入格式
输入两行,每行一个字符串。

第一行的字符串为 S,第二行的字符串为 T。

两个字符串均非空而且只包含大写英文字母。

输出格式
输出一个整数,表示答案。

数据范围
1≤|T|≤|S|≤1000
输入样例:
ABCDEABCD
XAABZ
输出样例:
3

分析:又是一个dp,用dp[i][j]表示s的前i个要包含t的前j个所需要修改的最少字符个数。
那么状态转移方程就是当s[i-1]==t[j-1]时,不需要修改 dp[i][j] = min(dp[i][j],dp[i-1][j-1])
s[i-1]!=t[j-1]时,dp[i][j] = min(dp[i-1][j-1]+1,dp[i-1][j])
前者表示修改当前的第j个字符,后者表示不修改。
需要注意的是因为是求最小,那么dp的初值为INF,在第一次更新时需要特殊处理一下。

代码:

#include 
#include 
using namespace std;
int dp[1005][1005];
#define INF 0x3f3f3f3f

int main()
{
     
    string s,t;
    cin>>s>>t;
    memset(dp,INF,sizeof(dp));
    for (int i = 1;i <= s.size() ;i++) {
     
        for (int j =1; j <= i ;j++){
     
            if(s[i-1] == t[j-1]){
     
                if(dp[i-1][j-1] == INF) dp[i][j] = min(dp[i][j],0);
                else dp[i][j] = min(dp[i][j],dp[i-1][j-1]);
            }
            else {
     
                if(dp[i-1][j-1]== INF)
                    dp[i][j] = min(1,dp[i-1][j]);
                else  dp[i][j] = min(dp[i-1][j-1]+1,dp[i-1][j]);
            }
            //cout<
        }
        //cout<
    }
    cout<<dp[s.size()][t.size()]<<endl;
    return 0;
}

G排列数

题目链接:https://www.acwing.com/problem/content/2556/
在一个排列中,一个折点是指排列中的一个元素,它同时小于两边的元素,或者同时大于两边的元素。

对于一个 1∼n 的排列,如果可以将这个排列中包含 t 个折点,则它称为一个 t+1 单调序列。

例如,排列 (1,4,2,3) 是一个 3 单调序列,其中 4 和 2 都是折点。

给定 n 和 k,请问 1∼n 的所有排列中有多少个 k 单调队列?

输入格式
输入一行包含两个整数 n,k。

输出格式
输出一个整数,表示答案。

答案可能很大,你可需要输出满足条件的排列数量除以 123456 的余数即可。

数据范围
1≤k≤n≤500
输入样例:
4 2
输出样例:
12

分析:找大佬问过之后终于懂了,正解是dp,需要画图找一下规律。
用dp[i][j]表示前i个有j个单调队列。

dp代码:

#include 
#include 
using namespace std;
#define INF 0x3f3f3f3f
#define mod 123456
int dp[505][505];
int n,k;

int main()
{
     
    cin.tie(0),ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i = 1;i <= n ;i++)
        dp[i][1] = 2;
    for(int i = 3; i <= n; i++) {
     
        for(int j = 2; j <= i-1; j++) {
     
            dp[i][j] =( dp[i-1][j-1] *2 + dp[i-1][j] * j +dp[i-1][j-2] *(i-j));
            dp[i][j] %= mod;
        }
    }
    cout<<dp[n][k]<<endl;
    return 0;
}

暴力的代码也仍然放在这里,因为时间复杂度极大,应该只能过一点点数据,
dfs+剪枝,搜索所有的全排列情况,在搜索的过程中,统计已经放置过的数前面位置出现过的拐点数t,对于已经列出的部分,如果已经超过k-1个拐点就剪枝掉。
又因为对于后n个数,最多有n-1个拐点,如果已经排列部分有的拐点数+后面未排列的部分的最多拐点数仍少于题目要求的k-1,剪枝掉。

代码:

#include 
#include 
using namespace std;
int vis[505];
int a[505];
int b[505];
#define INF 0x3f3f3f3f
#define mod 123456
int n,k;

void dfs(int x,int cnt, int t) {
     
    if( t+1 > k)
        return ;
    if(t+1+x-cnt < k-1)
        return;
    for(int i =1; i <= x; ++i){
     
        if(vis[i] == 1)
            continue;
        vis[i] = 1;
        a[cnt] = i;
        if(cnt >= 3){
     
            if((a[cnt-1] < a[cnt] && a[cnt-1] < a[cnt-2]) || (a[cnt-1] > a[cnt]&& a[cnt-1] > a[cnt-2]))
                t++;
        }
        if(x == cnt) {
     
            a[cnt] = i;
            b[t+1]++;
            b[t+1] %= mod;
            //cout<<"t+1="<
            vis[i] = 0;
            return ;
        }
        //cout<
        dfs(x,cnt+1,t);
        if(cnt >= 3&&((a[cnt-1]<a[cnt]&& a[cnt-1] <a[cnt-2]) || (a[cnt-1] >a[cnt]&& a[cnt-1] > a[cnt-2])))
            t--;
        vis[i] = 0;
    }
    return ;
}

int main()
{
     
    cin.tie(0),ios::sync_with_stdio(false);
    cin>>n>>k;
    if(k==1)
        cout<<2<<endl;
    else{
     
        dfs(n,1,0);
        cout<<b[k]<<endl;
    }
    return 0;
}

H解谜游戏

题目链接:https://www.acwing.com/problem/content/2557/
小明正在玩一款解谜游戏。

谜题由 24 根塑料棒组成,其中黄色塑料棒 4 根,红色 8 根,绿色 12 根 (后面用 Y 表示黄色、R 表示红色、G 表示绿色)。

初始时这些塑料棒排成三圈,如上图所示,外圈 12 根,中圈 8 根,内圈 4 根。

小明可以进行三种操作:

将三圈塑料棒都顺时针旋转一个单位。例如当前外圈从 0 点位置开始顺时针依次是 YRYGRYGRGGGG,中圈是 RGRGGRRY,内圈是 GGGR。那么顺时针旋转一次之后,外圈、中圈、内圈依次变为:GYRYGRYGRGGG、YRGRGGRR 和 RGGG。
将三圈塑料棒都逆时针旋转一个单位。例如当前外圈从 0 点位置开始顺时针依次是 YRYGRYGRGGGG,中圈是 RGRGGRRY,内圈是 GGGR。那么逆时针旋转一次之后,外圈、中圈、内圈依次变为:RYGRYGRGGGGY、GRGGRRYR 和 GGRG。
将三圈 0 点位置的塑料棒做一个轮换。具体来说:外圈 0 点塑料棒移动到内圈 0 点,内圈 0 点移动到中圈 0 点,中圈 0 点移动到外圈 0 点。例如当前外圈从 0 点位置开始顺时针依次是 YRYGRYGRGGGG,中圈是 RGRGGRRY,内圈是 GGGR。那么轮换一次之后,外圈、中圈、内圈依次变为:RRYGRYGRGGGG、GGRGGRRY 和 YGGR。
小明的目标是把所有绿色移动到外圈、所有红色移动中圈、所有黄色移动到内圈。

给定初始状态,请你判断小明是否可以达成目标?

输入格式
第一行包含一个整数 T,代表询问的组数。

每组询问包含 3 行:

第一行包含 12 个大写字母,代表外圈从 0 点位置开始顺时针每个塑料棒的颜色。

第二行包含 8 个大写字母,代表中圈从 0 点位置开始顺时针每个塑料棒的颜色。

第三行包含 4 个大写字母,代表内圈从 0 点位置开始顺时针每个塑料棒的颜色。

输出格式
对于每组询问,输出一行 YES 或者 NO,代表小明是否可以达成目标。

数据范围
1≤T≤100
输入样例:
2
GYGGGGGGGGGG
RGRRRRRR
YRYY
YGGGRRRRGGGY
YGGGRRRR
YGGG
输出样例:
YES
NO
分析:
容易看出对于每个字符串只能和对应位置i%4的值相等的地方交换位置,那么就可以分成4组,每组应该有3个G,2个R,1个Y,检查每组是否符合,用map存储一下方便处理。
代码:

#include 
#include 
using namespace std;
#define INF 0x3f3f3f3f
#define mod 123456
string s1,s2,s3;

int judge(int x){
     
    map<char,int>vis;
    vis[s1[x]]++;
    vis[s1[x+4]]++;
    vis[s1[x+8]]++;
    vis[s2[x]]++;
    vis[s2[x+4]]++;
    vis[s3[x]]++;
    if(vis['G']== 3 &&vis['R'] == 2 &&vis['Y'] ==1)
        return 1;
    return 0;
}

int main()
{
     
    int t;
    cin>>t;
    while(t--){
     
        cin>>s1>>s2>>s3;
        int f = 1;
        for(int i = 0; i < 4 ;i++) {
     
            if(!judge(i)) {
     
                f = 0;
                break;
            }
        }
        if(f)
            cout<<"YES"<<endl;
        else cout<<"NO"<<endl;

    }
    return 0;
}

I第八大奇迹

题目链接:https://www.acwing.com/problem/content/2558/
在一条 R 河流域,繁衍着一个古老的名族 Z。

他们世代沿河而居,也在河边发展出了璀璨的文明。

Z 族在 R 河沿岸修建了很多建筑,最近,他们热衷攀比起来。

他们总是在比谁的建筑建得最奇特。

幸好 Z 族人对奇特的理解都差不多,他们很快给每栋建筑都打了分,这样评选谁最奇特就轻而易举了。

于是,根据分值,大家很快评出了最奇特的建筑,称为大奇迹。

后来他们又陆续评选了第二奇特、第二奇特、……、第七奇特的建筑,依次称为第二大奇迹、第三大奇迹、……、第七大奇迹。

最近,他们开始评选第八奇特的建筑,准备命名为第八大奇迹。

在评选中,他们遇到了一些问题。

首先,Z 族一直在发展,有的建筑被拆除又建了新的建筑,新建筑的奇特值和原建筑不一样,这使得评选不那么容易了。

其次,Z 族的每个人所生活的范围可能不一样,他们见过的建筑并不是所有的建筑,他们坚持他们自己所看到的第八奇特的建筑就是第八大奇迹。

Z 族首领最近很头疼这个问题,他害怕因为意见不一致导致 Z 族发生分歧。

他找到你,他想先了解一下,民众自己认为的奇迹是怎样的。

现在告诉在 R 河周边的建筑的变化情况,以及在变化过程中一些人的生活范围,请编程求出每个人认为的第八大奇迹的奇特值是多少。

输入格式
输入的第一行包含两个整数 L,N,分别表示河流的长度和要你处理的信息的数量。开始时河流沿岸没有建筑,或者说所有的奇特值为 0。

接下来 N 行,每行一条你要处理的信息。

如果信息为 C p x,表示流域中第 p 个位置 (1≤p≤L) 建立了一个建筑,其奇特值为 x。如果这个位置原来有建筑,原来的建筑会被拆除。

如果信息为 Q a b,表示有个人生活的范围是河流的第 a 到 b 个位置(包含 a 和 b,a≤b),这时你要算出这个区间的第八大奇迹的奇特值,并输出。如果找不到第八大奇迹,输出 0。

输出格式
对于每个为 Q 的信息,你需要输出一个整数,表示区间中第八大奇迹的奇特值。

数据范围
1≤L≤100000,
1≤N≤100000,
所有奇特值为不超过 109 的非负整数。

输入样例:
10 15
C 1 10
C 2 20
C 3 30
C 4 40
C 5 50
C 6 60
C 7 70
C 8 80
C 9 90
C 10 100
Q 1 2
Q 1 10
Q 1 8
C 10 1
Q 1 10
输出样例:
0
30
10
20
分析:树状数组+整体二分,这种码量考场上直接放弃吧orz
代码:

#include
#include
using namespace std;
const int N=200010;
int n,L;
struct node
{
     
    int op,x,y,k;
    int id;
}q[N],rq[N],lq[N];
int ans[N];
int tree[N];
int now[N];
int lowbit(int x)
{
     
    return x&-x;
}
void change(int k,int x)
{
     
    for(;k<=L;k+=lowbit(k)) tree[k]+=x;
}
int query(int k)
{
     
    int res=0;
    for(;k;k-=lowbit(k)) res+=tree[k];
    return res;
}
void solve(int vl,int vr,int ql,int qr)
{
     
    if(ql>qr) return;
    if(vl==vr)
    {
     
        for(int i=ql;i<=qr;i++)
            if(q[i].op==2) ans[q[i].id]=vl;
        return;
    }
    int mid=vl+vr>>1;
    int l=0,r=0;
    for(int i=ql;i<=qr;i++)
    {
     
        if(q[i].op==1)
        {
     
            if(q[i].y<=mid) 
                lq[++l]=q[i];
            else 
                change(q[i].x,q[i].k),rq[++r]=q[i];
        }
        else
        {
     
            int tmp=query(q[i].y)-query(q[i].x-1);
            if(q[i].k<=tmp) 
                rq[++r]=q[i];
            else 
                q[i].k-=tmp,lq[++l]=q[i];
        }
    }
    for(int i=ql;i<=qr;i++)
        if(q[i].op==1&&q[i].y>mid)
            change(q[i].x,-q[i].k);
    for(int i=1;i<=l;i++) q[ql+i-1]=lq[i];
    for(int i=1;i<=r;i++) q[ql+l+i-1]=rq[i];
    solve(vl,mid,ql,ql+l-1);
    solve(mid+1,vr,ql+l,qr);
}
int main()
{
     
    cin>>L>>n;
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
     
        ans[i]=-1;
        char op;
        int x,y;
        cin>>op>>x>>y;
        if(op=='C')
        {
     
            if(now[x])
            {
     
                q[++cnt]={
     1,x,now[x],-1};
                q[++cnt]={
     1,x,y,1};
                now[x]=y;
            }
            else
            {
     
                q[++cnt]={
     1,x,y,1};
                now[x]=y;
            }
        }
        else 
            q[++cnt]={
     2,x,y,8,i};
    }
    solve(0,1e9,1,cnt);
    for(int i=1;i<=n;i++)
        if(ans[i]!=-1) cout<<ans[i]<<'\n';
    return 0;
}

J燃烧权杖

小 C 最近迷上了一款游戏。

现在,在游戏中,小 C 有一个英雄,生命值为 x;敌人也有一个英雄,生命值为 y。

除此以外,还有 k 个士兵,生命值分别为 a1、a2、……、ak。

现在小 C 打算使用一个叫做“燃烧权杖”的技能。

“燃烧权杖”会每次等概率随机选择一个活着的角色(英雄或士兵),扣减其 10 点生命值,然后如果该角色的生命值小于或等于 0,则该角色死亡,不会再被“燃烧权杖”选中。

“燃烧权杖”会重复做上述操作,直至任意一名英雄死亡。

小 C 想知道使用“燃烧权杖”后敌方英雄死亡(即,小 C 的英雄存活)的概率。

为了避免精度误差,你只需要输出答案模一个质数 p 的结果,具体见输出格式。

输入格式
输入包含多组数据。

输入第一行包含一个正整数 T,表示数据组数。

接下来 T 组,每组数据第一行包含四个非负整数 x、y、p、k,分别表示小 C 的英雄的生命值、敌方英雄的生命值,模数和士兵个数。

第二行包含 k 个正整数 a1、a2、……、ak,分别表示每个士兵的生命值。

输出格式
对于每组数据,输出一行一个非负整数,表示答案模质数 p 的余数。

可以证明,答案一定为有理数。设答案为 a/b(a 和 b 为互质的正整数),你输出的数为 x,则你需要保证 a 与 bx 模 p 同余;也即,x=(a⋅b−1)modp,其中 b−1 表示 b 模 p 的逆元, mod 为取模运算。

数据范围
1≤x,y,a1,…,ak≤109,
3≤p≤10000 且 p 为质数,
0≤k≤10
输入样例:
6
1 10 101 0

100 1 101 0

50 30 4903 2
1 1
987 654 233 1
321
1000000000 999999999 233 3
1 2 3
1000000000 999999999 3 3
1 2 3
输出样例:
51
37
1035
118
117
2
样例解释
对于第一组数据,所求概率即为“燃烧权杖”第一次就扣减敌方英雄 10 点生命值的概率,即 1/2。2×51 模 101 余 1。

对于第二组数据,答案为 1023/1024,1024×37 与 1023 模 101 同余。

对于第三组数据,答案为 99/128。

你可能感兴趣的:(蓝桥杯,算法,dfs,动态规划)