计蒜客2020蓝桥杯大学A组模拟赛题解

计蒜客2020蓝桥杯大学A组模拟赛题解

蓝桥杯的话,去年拿了C++组的国二。今年报名了新成立的Python组,不知道能不能摸到国一的鱼

模拟赛链接如下:

https://www.jisuanke.com/contest/6510?view=challenges

打蓝桥的策略依旧很简单:前面60~70min 搞填空,后面刷大题,注意先看完所有题,不要只按顺序


A. 计算周长

一个很显然的想法是a和b和c尽量接近。当然还是枚举出来更为靠谱。我们给V分解因子。穷举所有可能a,b,c取min即可

#include 
#include 
#include 
#define int long long

using namespace std;

vector<int> f,g;
int S=2E9;

signed main(){
    int V=932065482;
    for(int i=2;i*i<=V;i++)
        if(V%i==0)
            while(V%i==0) {
                f.push_back(i);
                V /= i;
            }
    if(V>1)
        f.push_back(V);
    do{
        g.clear();
        g.push_back(f[0]);
        for(int i=1;i<f.size();i++)
            g.push_back(g.back()*f[i]);
        for(int i=0;i<f.size();i++)
            for(int j=i;j<f.size();j++)
            {
                int a=g[i],b=g[j]/g[i],c=g.back()/g[j];
                S=min(S,2*(a*b+a*c+b*c));
                if(S==46925458)
                    printf("%lld %lld %lld\n",a,b,c);
            }
    }while(next_permutation(f.begin(),f.end()));
    printf("%lld",S);
    return 0;
}

B. 七巧板

第二题让我有些犯难,这个直接用程序搞似乎不是很容易。能不能手算呢?

从平面分割数想起,

加一条直线,最多可以增加6个区域

那么第二条似乎最多可以增加到7个区域了,那么答案是不是7+6+7+8+9+10呢?

想要验证但是时间不多,先填上就好。事实上,47就是正确的

C. 苹果

又是一道比较难搞的题。

很快就放弃了手算。事实上,如果你足够老司机,那么就会想起2017 ICPC广西邀请赛中,对子和顺子那题。唯一的区别在于,对子的组成从2个一组,成了3个一组。

那题可以贪心。这题中,优先组“对子”的优势似乎并不太明显,因此我没有决定贪心(貌似某些贪心也能得到62,但正确性依旧不是很明显,先留坑)。

好在用dp求解的方法基本不变。我们用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示到第i个篮子,预留 ( i − 1 , i , i + 1 ) (i-1,i,i+1) (i1,i,i+1)的个数为j,预留 ( i , i + 1 , i + 2 ) (i,i+1,i+2) (i,i+1,i+2)的个数为k,那么转移方程如下

d p [ i + 1 ] [ k ] [ l ] = m a x ( d p [ i + 1 ] [ k ] [ l ] , d p [ i ] [ j ] [ k ] + ( a [ i + 1 ] − j − k − l ) / 3 + j ) ; dp[i + 1][k][l] = max(dp[i + 1][k][l], dp[i][j][k] + (a[i + 1] - j - k - l) / 3 + j); dp[i+1][k][l]=max(dp[i+1][k][l],dp[i][j][k]+(a[i+1]jkl)/3+j);

我们看到对于第i+1步,去掉 j + k + l j+k+l j+k+l剩下的我们尽量组对,然后加上已经能成顺子的 j j j即是一种可行方案。

另外对于 j , k j,k j,k上限,实际上显然能看出来不超过2。

#include 
#include 

using namespace std;

int dp[38][3][3], a[38] = {0, 7, 2, 12, 5, 9, 9, 8, 10, 7, 10, 5, 4, 5, 8, 4, 4, 10, 11, 3, 8, 7, 8, 3, 2, 1, 6, 3,
                           9, 7, 1}, ans;

int main() {
    for (int i = 0; i <= 30; i++)
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                dp[i][j][k] = -1E9;
    dp[0][0][0] = 0;
    for (int i = 0; i < 30; i++)
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                for (int l = 0; l < 3; l++)
                    if (j + k + l <= a[i + 1])
                        dp[i + 1][k][l] = max(dp[i + 1][k][l], dp[i][j][k] + (a[i + 1] - j - k - l) / 3 + j);
    for (int j = 0; j < 3; j++)
        for (int k = 0; k < 3; k++)
            ans = max(ans, dp[30][j][k]);
    printf("%d", ans);
    return 0;
}

当然,对于大部分人来说,短时间在没有经验的情况下,想好一个不错的状态并且dp有点困难,那么不妨尝试一下暴力搜索加剪枝,代码如下,大概跑完需要将近十分钟(慢慢扩大数据范围来估计时间,要有耐心咯)。

#include 
#include 

using namespace std;

int a[38] = {0, 7, 2, 12, 5, 9, 9, 8, 10, 7, 10, 5, 4, 5, 8, 4, 4, 10, 11, 3, 8, 7, 8, 3, 2, 1, 6, 3, 9, 7,
                        1};

int ans;

void dfs(int x, int res) {
    if (x > 30) {
        ans = max(ans, res);
        return;
    }
    if(x>5&&a[x-5]&&a[x-4]&&a[x-3])  //假如留下了连续三个,那么为啥不组顺子呢
        return ;
    if(x>4&&a[x-4]>2)  //假如某个位置比3大当然也不可以
        return ;
    for (int j = a[x]/3; a[x] - j * 3 <= 4; j--) {  //不组顺
        a[x] -= j * 3;
        dfs(x + 1, res + j);
        a[x] += j * 3;
    }
    if (x > 2)  //组顺
        for (int i = 1; i <= a[x] && i < 3; i++)
            if (a[x - 2] >= i && a[x - 1] >= i && a[x] >= i) {
                a[x - 2] -= i;
                a[x - 1] -= i;
                a[x]-=i;
                for (int j = a[x]/3; a[x] - j * 3 <= 4; j--) {
                    a[x] -= j * 3;
                    dfs(x + 1, res + j+i);
                    a[x] += j * 3;
                }
                a[x - 2] += i;
                a[x - 1] += i;
                a[x] += i;
            }
}

int main() {
    dfs(1, 0);
    printf("%d", ans);
    return 0;
}

D. 天气与活动

学过自然语言处理的同学们都知道这个叫隐马尔可夫链,因为数据不大,我们仅仅采用贝叶斯公式就足够了

P ( [ 晴 天 , 雨 天 , 雨 天 ] ∣ [ 散 步 , 购 物 , 打 扫 卫 生 ] ) = P ( Q ∣ O ) = P ( Q ) ∗ P ( O ∣ Q ) ∑ q P ( O ∣ q ) P ( q ) P([晴天,雨天,雨天]|[散步,购物,打扫卫生])=P(Q|O)=\frac{P(Q)*P(O|Q)}{\sum_q P(O|q)P(q)} P([,,][,,])=P(QO)=qP(Oq)P(q)P(Q)P(OQ)

分 子 = ( 0.6 ∗ 0.4 ∗ 0.7 ) ∗ ( 0.6 ∗ 0.4 ∗ 0.5 ) = 0.168 ∗ 0.12 = 0.02016 分子=(0.6*0.4*0.7)*(0.6*0.4*0.5)=0.168*0.12=0.02016 =(0.60.40.7)(0.60.40.5)=0.1680.12=0.02016

分母有八种情况,我们此处用程序枚举即可,大约为 0.043928 0.043928 0.043928

#include 

using namespace std;

double o[2][3] = {{0.6, 0.3, 0.1},
                  {0.1, 0.4, 0.5}};
double q[2][2] = {{0.6, 0.4},
                  {0.3, 0.7}};
double ans;

int main() {
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 2; j++)
            for (int k = 0; k < 2; k++) {
                ans += o[i][0] * o[i][1] * o[i][2]*q[0][i]*q[i][j]*q[j][k];
            }
    printf("%lf",ans);
    return 0;
}

因此答案为 0.45893 0.45893 0.45893

E. 方阵

又是一道有点恼人的题目,当然你可以写一个不那么暴力的暴力,然后等待许久,比较保险。足够自信或者没有耐心的话,可以写正解,方法是二维前缀和。代码如下

#include 
#include 
#include 

using namespace std;

long long ans;
int sum[1005][1005];

int area(int a, int b, int c, int d){
    a+=500,b+=500,c+=501,d+=501;
    return sum[c][d]+sum[a][b]-sum[c][b]-sum[a][d];
}

int main() {
    for (int i = -500; i <= 500; i++)
        for (int j = -500; j <= 500; j++)
            if (i * i + j * j <= 500 * 500 && __gcd(i, j) == 1)
                sum[i+501][j+501]++;
    for (int i = 1; i <= 1001; i++)
        for (int j = 1; j <= 1001; j++)
            sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
    for (int i = 1; i <= 1000; i++)
        for (int j = 1; j <= 1000; j++)
            ans+=area(max(-500,1-i),max(-500,1-j),min(1000-i,500),min(1000-j,500));
    printf("%lld",ans%1000000007);
    return 0;
}

注意为了方便测试小数据,1000,500这些常量还是赋值比较好,以便改动。


由于参加的是python组,以下代码均为python书写,可能会发生超时。

参加蓝桥杯时,大题的实现,务必要多加小心,不可以有低级错误!


F. 被袭击的村庄

基本上是一道模拟裸题,不多叙述。

这一题题意有误,后来听说是前面三个数实际上应该三种建筑的耐久度为0的总数。

n,m=map(int,input().split())
a=[0]+[int(x) for x in input().split()]
k,w=map(int,input().split())
A=[[0]]
for i in range(1,k+1):
    A.append([0]+[int(x) for x in input().split()])

N=[[0]]
M=[[0]*(m+1) for i in range(n+1)]
for i in range(1,n+1):
    N.append([0]+[int(x) for x in input().split()])
    for j in range(1,m+1):
        M[i][j]=a[N[i][j]]

q=int(input())

cnt=[0]*4
ans=0

for i in range(q):
    o,x,y=map(int,input().split())
    up = max(x - k // 2, 1); dn = min(x + k // 2, n); le = max(y - k // 2, 1); ri = min(y + k // 2, m)
    for i in range(up,dn+1):
        for j in range(le,ri+1):
            M[i][j] = max(M[i][j] - A[i - (x - k // 2) + 1][j - (y - k // 2) + 1], 0)
    if not o:
        for i in range(up,dn+1):
            for j in range(le,ri+1):
                for fx in range(-1,2):
                    for fy in range(-1,2):
                        if fx or fy:
                            u=i+fx;v=j+fy
                            if u > 0 and u <= n and v > 0 and v <= m:
                                M[u][v] = max(M[u][v] - w, 0)
for i in range(1,n+1):
    for j in range(1,m+1):
        if not M[i][j]:
            cnt[N[i][j]]+=1
        ans+=a[N[i][j]]-M[i][j]
print(cnt[1],cnt[2],cnt[3])
print(ans)

G. 建立联系

实际上就是一道克鲁斯卡尔算法求生成树的问题,不同的是只需要做到剩下k个强连通分量即可退出。

有人可能会怀疑这样贪心为什么可以呢?

这里暂时留坑。不过既然克鲁斯卡尔算法成立,那么猜测类似的算法也是很有道理的。

n,m,k=map(int,input().split())
E=[]
for i in range(m):
    a,b,c=map(int,input().split())
    E.append((c,a,b))

E.sort()
cnt=n
fa=[i for i in range(0,n+1)]

def fis(x):
    if fa[x]==x:
        return x
    fa[x]=fis(fa[x])
    return fa[x]

ans=0

for i in range(m):
    if fis(E[i][1])!=fis(E[i][2]):
        ans+=E[i][0]
        fa[fis(E[i][1])]=fis(E[i][2])
        cnt-=1
        if cnt<=k:
            print(ans)
            exit(0)
print(ans)

H. 最短路

应该算经典题目了吧。从起点对正图和反图各自求一次单源最短路,然后所有值相加即可。反图的单源最短路必然和正图的每个点到源的最短路对应

from queue import PriorityQueue

class Dij:
    def solve(self,E,n,s):
        self.ans=[int(1E18)]*(n+1)
        self.ans[s]=0
        Q=PriorityQueue()
        Q.put((0,s))
        while not Q.empty():
            w,u=Q.get()
            if self.ans[u]<w:
                continue
            for i in E[u]:
                if self.ans[i[0]]>self.ans[u]+i[1]:
                    self.ans[i[0]]=self.ans[u]+i[1]
                    Q.put((self.ans[i[0]],i[0]))

A=Dij()
B=Dij()

T=int(input())
for i in range(T):
    n,m=map(int,input().split())
    E=[[] for i in range(n+1)]
    F=[[] for i in range(n+1)]
    for i in range(m):
        u,v,w=map(int,input().split())
        E[u].append((v,w))
        F[v].append((u,w))
    A.solve(E,n,1)
    B.solve(F,n,1)
    ans=0
    for i in range(2,n+1):
        ans+=A.ans[i]+B.ans[i]
    print(ans)

I. 迷宫

这一题是一个典型的广搜。注意理解题意,题目中如果一个点有传送门,将会立即进行传送,而不能转而进行上下左右移动(当时理解错了,真坑)。于是,每一次四个方向扩展新状态,如果有传送门,我们需要把传送门尽头的那个点扔进队列,否则把自身扔进队列。在传送门中跳跃时不断判断是否已经到达终点。

实现中细节还是很多的,要理清思路再写。

class queue:
    def __init__(self,n):
        self.q=[0]*n
        self.hd=0
        self.tl=0

    def put(self,x):
        self.q[self.tl]=x
        self.tl+=1

    def get(self):
        self.hd+=1
        return self.q[self.hd-1]

    def empty(self):
        return self.hd==self.tl

n,m=map(int,input().split())
s=[[0]]
for i in range(1,n+1):
    s.append("0"+input())
q=int(input())
U=dict()
for i in range(q):
    a,b,c,d=map(int,input().split())
    U[(a,b)]=(c,d)
p=list(map(int,input().split()))
Q=queue(1000000-m)
vis=[[False]*1001 for i in range(1001)]

def extend(x,y,stp):
    f=False
    while s[x][y]!='*' and not vis[x][y] and U.__contains__((x,y)):
        if [x,y]==p:
            print(stp)
            exit(0)
        vis[x][y]=True
        x,y=U[(x,y)]
        f=True
    if [x,y]==p:
        print(stp)
        exit(0)
    if s[x][y]!='*' and not vis[x][y]:
        Q.put((x,y,stp))
        vis[x][y]=True
    return f
        
extend(1,1,0)
while not Q.empty():
    x,y,stp=Q.get()
    for j in [(0,1),(1,0),(0,-1),(-1,0)]:
        u=x+j[0];v=y+j[1]
        if u>0 and u<=n and v>0 and v<=m and s[u][v]!='*' and not vis[u][v]:
            extend(u,v,stp+1)
print('No solution')

J. 涂墙

这一题是一道难题,基本上也符合蓝桥杯最后一题的定位。赛场上最后一题,不要浪费时间想着正解,赶紧把分水水到就行了。

回忆一下一维的情况。有箱子中1到n编号的小球,我们每次有放回地取,期望多少次能取到所有小球呢?

当我们首次取到x个小球时,下一个小球在x之外的概率为 ( n − x ) / n (n-x)/n (nx)/n,因此,能取到新球的期望次数为 n / ( n − x ) n/(n-x) n/(nx),因此,总期望等于 ∑ i = 0 n − 1 n / ( n − i ) \sum_{i=0}^{n-1} n/(n-i) i=0n1n/(ni)

(附:如果这里每个小球取出的概率不同,则需要用到min/max容斥来解)

那么对于二维的情况呢?

我们假设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示首次填下i行j列经过的期望步数,不难发现,可以由左下的三种状态转移而来,但三种情况的概率,似乎并不容易求解。经过一番猜测也没有成功。

参考另一位博主的解法:https://blog.csdn.net/JiangHxin/article/details/104059688

发现原来如此巧妙。将正向的状态改成定义为逆向的状态,把dp定义为已经填满i行和j列的前提下,转移方程便成了另一幅模样,

d p [ i ] [ j ] = i / n ∗ j / n ∗ d p [ i ] [ j ] + i / n ∗ ( n − j ) / n ∗ d p [ i ] [ j + 1 ] + ( n − i ) / n ∗ j / n ∗ d p [ i + 1 ] [ j ] + ( n − i ) / n ∗ ( n − j ) / n ∗ d p [ i + 1 ] [ j + 1 ] ; dp[i][j] = i/n * j/n * dp[i][j] + i/n * (n-j)/n * dp[i][j+1] + (n-i)/n * j/n * dp[i+1][j] + (n-i)/n * (n-j)/n * dp[i+1][j+1] ; dp[i][j]=i/nj/ndp[i][j]+i/n(nj)/ndp[i][j+1]+(ni)/nj/ndp[i+1][j]+(ni)/n(nj)/ndp[i+1][j+1];

其中左右都包含 d p [ i ] [ j ] dp[i][j] dp[i][j]这一项,没有关系,移项求解即可。

这里状态定义和巧妙的方程求解是需要品味的。

def solve():
    n,m=map(int,input().split())
    X=[0]*(n+1)
    Y=[0]*(n+1)
    a=0;b=0
    for i in range(m):
        x,y=map(int,input().split())
        if not X[x]:
            X[x]=1
            a+=1
        if not Y[y]:
            Y[y]=1
            b+=1
    dp=[[0]*(n+2) for i in range(n+2)]
    for i in range(n,a-1,-1):
        for j in range(n,b-1,-1):
            x=n*n-i*j
            if x:
                dp[i][j]=i*(n-j)/x*dp[i][j+1]+(n-i)*j/x*dp[i+1][j]+(n-i)*(n-j)/x*dp[i+1][j+1]+1.0*n*n/x
    print(dp[a][b])    

T=int(input())
for i in range(T):
    solve()

你可能感兴趣的:(其他)