第十二届蓝桥杯软件类 C/C++ A组 个人解答(基本完结)

  • Post author:
  • Post category:其他




A:卡片

题目

在这里插入图片描述


答案

:3181

#include <iostream>

using namespace std;

const int N=100;

int n;
int st[N];

int split(int n)
{
    while(n)
    {
        if(--st[n%10]<0) return false;
        n/=10;
    }
    return true;
}

int main()
{
    n=2021;
    for(int i=0;i<10;i++) st[i]=n;

    int i=1;
    while(split(i)) i++;

    cout<<i-1<<endl;

    return 0;
}



B:直线


题目


在这里插入图片描述


题解

  • 常规思路用斜率,但这里斜率相同的可能也是不同的直线,由此我们可以存一下该斜率在 y 轴上的截距,就能唯一判断了
  • 特殊处理



    x

    1

    x

    2

    =

    0

    x1-x2=0






    x


    1













    x


    2




    =








    0





    的点(斜率为无穷):直接加上竖线条数即可

  • 也可以先弄出所有直线,排个序方便去重一些,这里就不写了


答案

: 40257

#include <iostream>
#include <cmath>

using namespace std;

const int N=1000010;
const double eps=1e-6;

int n,m;
double st[N],d[N];
int cnt;

bool check(double k,double t)
{
    for(int i=0;i<cnt;i++)
    {
        if(abs(st[i]-k)<eps&&abs(d[i]-t)<eps) return false;
    }
    d[cnt]=t;
    st[cnt++]=k;
    return true;
}

int main()
{
    //n=20,m=21;
    //n=3,m=4; //35
    //n=2,m=3; //11
    cin>>n;
    m=n+1;

    int res=0;
    for(int x1=0;x1<n;x1++)
        for(int y1=0;y1<m;y1++)
            for(int x2=0;x2<n;x2++)
                for(int y2=0;y2<m;y2++)
                {
                    if(x1==x2) continue;
                    double dx=x1-x2,dy=y1-y2;
                    double k=dy/dx;
                    if(check(k,y1-k*x1)) res++;
                }
    cout<<res+n<<endl;
    return 0;
}



C:货物摆放

在这里插入图片描述


答案

:2430


算法1:

枚举约数法,因为乘积等于 n 只可能是 n 的约数,因此我们可以先预处理出所有的约数,最后



n

3

n^3







n










3












枚举即可(只有128个约数)

#include <iostream>
#include <vector>

using namespace std;

typedef long long LL;

LL n;

int main()
{
    n=2021041820210418;
    vector<LL> a;

    for(LL i=1;i*i<=n;i++)
    {
        if(n%i==0)
        {
            a.push_back(i);
            a.push_back(n/i);
        }
    }

    int res=0;
    for(auto i:a)
        for(auto j:a)
            for(auto k:a)
                if(i*j*k==n) res++;

    cout<<res<<endl;

    return 0;
}


算法2:

直接暴力法(也就4s而已)

#include <iostream>

using namespace std;

typedef long long LL;

LL n;

int main()
{
    n=2021041820210418;

    int res=0;
    for(LL i=1;i*i*i<=n;i++)
        if(n%i==0)
            for(LL j=i;i*j*j<=n;j++) //j=i开始,保持递增
                if(n/i%j==0)
                {
                    LL k=n/i/j;
                    if(i==j&&i==k) res++;
                    else if(i==j||i==k||j==k) res+=3;
                    else res+=6;
                }

    cout<<res<<endl;

    return 0;
}


算法3:

筛质因子+手算排列组合比较快,也可以 dfs,但是要处理一下重复

筛质因子代码就不展示了
质因子及个数如下:
5882353 1
2857 1
131 1
2 1
17 1
3 3



D:路径

在这里插入图片描述


题解


知识点:最短路,dijkstra

  • 最短路问题,涉及到求最小公倍数



    l

    c

    m

    (

    a

    ,

    b

    )

    =

    a

    b

    /

    g

    c

    d

    (

    a

    ,

    b

    )

    lcm(a,b)=a*b/gcd(a,b)






    l


    c


    m


    (


    a


    ,




    b


    )




    =








    a













    b


    /


    g


    c


    d


    (


    a


    ,




    b


    )




  • 听一些大佬说dp也可以,但是不清楚正确性,即是否一定从前往后走。
  • 由于本题数据范围很小加上是填空题,直接最暴力的



    O

    (

    n

    2

    )

    O(n^2)






    O


    (



    n










    2









    )





    迪杰即可。如果边过多或者



    n

    2

    n^2







    n










    2












    超时可以考虑堆优化迪杰


答案

:10266837

#include <iostream>
#include <cstring>

using namespace std;

const int N=2050;

int g[N][N],n;
bool st[N];
int dis[N];

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

void djs()
{
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    for(int k=0;k<n;k++)
    {
        int u=0;
        for(int i=1;i<=n;i++)
        {
            if(!st[i]&&dis[i]<dis[u]) u=i;
        }
        st[u]=true;
        if(u==2021) break;
        for(int i=1;i<=n;i++) dis[i]=min(dis[i],dis[u]+g[u][i]);
    }
}

int main()
{
    n=2021;
    for(int i=1;i<N;i++)
        for(int j=i;j<N;j++)
        {
            if(j-i>21) g[i][j]=g[j][i]=0x3f3f3f3f;
            else g[i][j]=g[j][i]=i*j/gcd(i,j);
        }

    djs();

    cout<<dis[n]<<endl;

    return 0;
}



E:回路计数

在这里插入图片描述


题解

  • 直接爆搜时间复杂度是阶乘级别的,即



    O

    (

    n

    !

    )

    O(n!)






    O


    (


    n


    !)





    ,大概只能搜到15就不行了,原因在于每个点搜索顺序可以不一样,近似看成 n 的全排列就是阶乘级别了。

  • 仔细分析题目会发现每个点的状态我们都要处理到位,且要经过每个点,20是非常nice的一个数字,会想到什么:

    状态压缩

    ,我们不妨用



    (

    1

    <

    <

    21

    )

    1

    (1<<21)-1






    (


    1




    <<








    21


    )













    1





    内的每个数字来表示所有状态,但是会发现如果只这样是不好处理的,因此再加入一维。


  • 状态表示





    d

    p

    [

    s

    t

    a

    t

    e

    ]

    [

    j

    ]

    dp[state][j]






    d


    p


    [


    s


    t


    a


    t


    e


    ]


    [


    j


    ]





    表示当前经过了的点状态为



    s

    t

    a

    t

    e

    state






    s


    t


    a


    t


    e





    且最后一次经过的点为



    j

    j






    j





    的方案数。


  • 状态转移





    d

    p

    [

    s

    t

    a

    t

    e

    ]

    [

    j

    ]

    =

    i

    =

    0

    20

    d

    p

    [

    s

    t

    a

    t

    e

    2

    i

    ]

    [

    k

    ]

    dp[state][j]= \sum_{i=0}^{20}dp[state-2^i][k]






    d


    p


    [


    s


    t


    a


    t


    e


    ]


    [


    j


    ]




    =





















    i


    =


    0










    20





















    d


    p


    [


    s


    t


    a


    t


    e














    2










    i









    ]


    [


    k


    ]







    转移条件





    s

    t

    a

    t

    e

    state






    s


    t


    a


    t


    e





    的第



    j

    j






    j





    位是 1 并且



    s

    t

    a

    t

    e

    state






    s


    t


    a


    t


    e





    的第



    k

    k






    k





    位也是 1,



    j

    !

    =

    k

    j!=k






    j


    !




    =








    k









    g

    c

    d

    (

    i

    +

    1

    ,

    k

    +

    1

    )

    =

    =

    1

    gcd(i+1,k+1)==1






    g


    c


    d


    (


    i




    +








    1


    ,




    k




    +








    1


    )




    ==








    1






  • 结果表示





    r

    e

    s

    =

    i

    =

    0

    20

    d

    p

    [

    (

    1

    <

    <

    21

    )

    1

    ]

    [

    i

    ]

    res=\sum_{i=0}^{20}dp[(1<<21)-1][i]






    res




    =





















    i


    =


    0










    20





















    d


    p


    [(


    1




    <<








    21


    )













    1


    ]


    [


    i


    ]





    。有的小伙伴可能会问原因:



    d

    p

    [

    (

    1

    <

    <

    21

    )

    1

    ]

    [

    i

    ]

    dp[(1<<21)-1][i]






    d


    p


    [(


    1




    <<








    21


    )













    1


    ]


    [


    i


    ]





    表示的是经过了所有点且最后在第



    i

    i






    i





    个位置上的方案数,现在并没有回到源点,也就是说最后的所有结尾点为



    i

    i






    i





    的方案中就是最后倒数第2个点,再往前走一步就是终点(原点)了,而最后的状态是由所有倒数第2的状态转移过来,所以将所有



    d

    p

    [

    (

    1

    <

    <

    21

    )

    1

    ]

    [

    i

    ]

    dp[(1<<21)-1][i]






    d


    p


    [(


    1




    <<








    21


    )













    1


    ]


    [


    i


    ]





    加起来就是最终回到原点的方案数,也就是哈密顿回路的方案数。


答案

:881012367360

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; i++)

using namespace std;

const int INF=0x3f3f3f3f,MOD=1000000007,P=131,N=22,M=400010;

int n=21;
long long dp[1<<21][21];
bool g[N][N];

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

int main()
{
    rep(i,1,n)
        rep(j,i+1,n)
            if(gcd(i,j)==1) g[i][j]=g[j][i]=true;

    dp[1][0]=1;
    rep(i,2,1<<n)
        rep(j,0,20)
            if(i>>j&1)
                rep(k,0,20)
                    if(j!=k&&g[j+1][k+1]&&(i>>k&1))
                        dp[i][j]+=dp[i-(1<<j)][k];

    long long res=0;
    rep(i,0,20) res+=dp[(1<<21)-1][i];

    cout<<res<<endl;

    return 0;
}




F:砝码称重

在这里插入图片描述

在这里插入图片描述


题解


dp 可以做,枚举每一个砝码,再枚举所有已经存在的重量,加减当前重量

a[i]

即可,复杂度



O

(

n

m

)

O(nm)






O


(


nm


)






状态方程:

t[j]=t[j-a[i]]=true,t[j+a[i]]=true

; 用前面轮的结果标记这一轮。

众所周知:一维肯定快点(滚动数组)

一维:

#include <iostream>
#include <cstring>

using namespace std;

const int N=110,M=200010,B=M/2;

int a[N],n;
bool f[M],t[M];

int main()
{
    cin>>n;
    int m=0;
    for(int i=1;i<=n;i++) cin>>a[i],m+=a[i];
    
    f[B]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=-m;j<=m;j++)
            if(f[j+B]) 
            {
                t[j+B]=true;
                t[j+a[i]+B]=t[j-a[i]+B]=true;
            }
        memcpy(f,t,sizeof t);
    }
    
    int res=0;
    for(int i=1;i<=m;i++) res+=f[i+B];
    cout<<res<<endl;
    
    return 0;
}

二维:

#include <iostream>

using namespace std;

const int N=110,M=200010,B=M/2;

bool f[N][M];
int a[N],n;

int main()
{
    cin>>n;
    int m=0;
    for(int i=1;i<=n;i++) cin>>a[i],m+=a[i];

    f[0][B]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=-m;j<=m;j++)
        {
            if(f[i-1][j+B]) f[i][j+B]=true; //不选
            if(f[i-1][j+B])
                f[i][j+a[i]+B]=f[i][j-a[i]+B]=true;
        }
    }

    int res=0;
    for(int i=1;i<=m;i++) res+=f[n][i+B];
    cout<<res<<endl;

    return 0;
}



G:异或数列

在这里插入图片描述

在这里插入图片描述


题解


知识点:博弈论

首先这类题咱们自己可以比划比划,可以找到一些性质

  • 如果所有数的异或和为 0,那么所有位的值都为偶数,每个数不管怎么选,双方的数最终都会相等,必然是平局
  • 不是平局,必然异或和有一个1,我们找到最高位的1,统计数列中这位为1的数量



    c

    n

    t

    1

    cnt1






    c


    n


    t


    1





    ,它必定是奇数。如果如果Alice(先手)想赢,必然要使最高位掌握在自己手中,这里还有一个点就是这一位为0的元素数量



    c

    n

    t

    0

    cnt0






    c


    n


    t


    0





    ,如果打不赢的时候,使用这一位为0的数可以使得攻防反转,最终我们要统计的就是



    c

    n

    t

    1

    +

    c

    n

    t

    0

    cnt1+cnt0






    c


    n


    t


    1




    +








    c


    n


    t


    0





    的数量,如果是奇数,则Alice必胜,反之Bob必胜

  • 值得注意的最后一点就是如果该异或值的最高位只有一位1,那么选了这个1后无论后面怎么选都是选了这个1的最大,因此如果



    c

    n

    t

    1

    =

    0

    cnt1=0






    c


    n


    t


    1




    =








    0





    ,则 Alice 必胜,这个情况要先判。

#include <bits/stdc++.h>
#define x first
#define y second
#define all(x) x.begin(),x.end()
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
using namespace std;
typedef pair<int,int> PII;
typedef pair<pair<int,int>,int> PIII;
typedef long long LL;
typedef unsigned long long ULL;
const int INF=0x3f3f3f3f,MOD=1000000007,P=131,N=200010,M=200010;

int n;
int w[N];

int main()
{
    int T; cin>>T;
    while(T--)
    {
        scanf("%d",&n);

        int res=0;
        rep(i,1,n)
        {
            scanf("%d",&w[i]);
            res^=w[i];
        }
        if(res==0) puts("0");
        else
        {
            int k=30,cnt1=0,cnt0=0;
            while(!(res>>k&1)) k--;
            rep(i,1,n)
            {
                if(w[i]>>k&1) cnt1++;
                else cnt0++;
            }

            if(cnt1==1) puts("1");
            else if(n&1) puts("1");
            else puts("-1");
        }
    }

	return 0;
}



H:左孩子右兄弟

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


题解


知识点:树的遍历,树形DP

这题是给定一棵树,问怎么转可以使得树的深度最大

  • 首先孩子可以任意选,那么对于任意一个树上的结点,我们找到它的所有的子树中最深的一个孩子,然后统计它的孩子数量,把最深的这个孩子结点依次接到它所有孩子后面就可以了。例如:有三个子树,深度分别是 2,3,4,那么我们答案就是 4+1(接在2孩子上)+1(再接在3孩子上) = 6
#include <bits/stdc++.h>
#define x first
#define y second
#define all(x) x.begin(),x.end()
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
using namespace std;
typedef pair<int,int> PII;
typedef pair<pair<int,int>,int> PIII;
typedef long long LL;
typedef unsigned long long ULL;
const int INF=0x3f3f3f3f,MOD=1000000007,P=131,N=100010,M=200010;

int n;
int h[N],e[M],ne[M],idx;

void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int dfs(int u)
{
    int cnt=0,maxv=0;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        cnt++;
        maxv=max(maxv,dfs(j));
    }

    return maxv+cnt;
}

int main()
{
    memset(h,-1,sizeof h);
    cin>>n;
    rep(i,2,n)
    {
        int x; scanf("%d",&x);
        add(x,i);
    }

    cout<<dfs(1)<<endl;

	return 0;
}



I:括号序列

在这里插入图片描述

在这里插入图片描述


题解


这题才发现是个原题,可惜当时没写出来

  • 分别计算插左右括号的方案数
  • 计算另一种括号数的时候直接翻转再翻转符号就行了
  • 最后计算总方案数,其实就是插空法,分别插左右括号,最后相乘即可


DP分析:



状态表示:




f

[

i

]

[

j

]

f[i][j]






f


[


i


]


[


j


]





:只考虑前i个括号并且左括号比右括号多



j

j






j





的方案数


状态转移:






s

[

i

]

=

s[i]=






s


[


i


]




=





‘(’ ,



f

[

i

]

[

j

]

=

f

[

i

1

]

[

j

1

]

f[i][j]=f[i-1][j-1]






f


[


i


]


[


j


]




=








f


[


i













1


]


[


j













1


]





;

反之



f

[

i

]

[

j

]

=

f

[

i

1

]

[

j

+

1

]

+

f

[

i

]

[

j

1

]

f[i][j]=f[i-1][j+1]+f[i][j-1]






f


[


i


]


[


j


]




=








f


[


i













1


]


[


j




+








1


]




+








f


[


i


]


[


j













1


]





;(推导过程与完全背包类似)

  • 这里有个小问题就是有可能最终的答案刚好取模后 =0 造成返回值错误,但是这种情况十分罕见,可以忽略不计,也可以在官网通过,如果要避免这种情况可以参考

    代码2

    ,通过计算最少需要添加的括号数



    c

    n

    t

    cnt






    c


    n


    t





    ,然后再计算出答案是需要返回几

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N=5010,MOD=1e9+7;

LL f[N][N];
int n;
char s[N];

LL get()
{
    memset(f,0,sizeof f);
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        if(s[i]=='(')
        {
            for(int j=1;j<=n;j++)
                f[i][j]=f[i-1][j-1];
        }
        else
        {
            f[i][0]=(f[i-1][1]+f[i-1][0])%MOD;
            for(int j=1;j<=n;j++)
                f[i][j]=(f[i-1][j+1]+f[i][j-1])%MOD;
        }
    }
    for(int i=0;i<=n;i++)
        if(f[n][i]) return f[n][i];
    return -1;
}

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);

    LL a=get();

    reverse(s+1,s+n+1);
    for(int i=1;i<=n;i++)
        if(s[i]=='(') s[i]=')';
        else s[i]='(';

    LL b=get();

    cout<<a*b%MOD<<endl;

    return 0;
}

代码2:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f,MOD=1000000007,P=131,N=5010,M=2*N;

char s[N]; 
int n;
LL f[N][N];

LL calc()
{
	int cnt=0, k=0, l=0, r=0;
	for(int i=1;i<=n;i++)
	{
		if(s[i]==')') 
		{
			r++, k++;
			if(k>0) k=0,cnt++;
		}
		else l++,k--;
	}
	
	memset(f,0,sizeof f);
	f[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		if(s[i]=='(') 
		{
			for(int j=1;j<=n;j++)
				f[i][j]=f[i-1][j-1];
		}
		else
		{
			f[i][0]=(f[i-1][1]+f[i-1][0])%MOD;
			for(int j=1;j<=n;j++)
				f[i][j]=(f[i-1][j+1]+f[i][j-1])%MOD;
		}
	}
	
	return f[n][cnt+l-r]; //直接返回答案
}

int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	
	LL l=calc();
	reverse(s+1,s+n+1);
	for(int i=1;i<=n;i++) 
		if(s[i]==')') s[i]='(';
		else s[i]=')';
		
	LL r=calc();
	cout<<l*r%MOD<<endl;
	
	return 0;
}



J:分果果(题目)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

后续待官网题更新后再更新…心态爆炸



版权声明:本文为qq_29353083原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。