蓝桥杯的话,去年拿了C++组的国二。今年报名了新成立的Python组,不知道能不能摸到国一的鱼
模拟赛链接如下:
https://www.jisuanke.com/contest/6510?view=challenges
打蓝桥的策略依旧很简单:前面60~70min 搞填空,后面刷大题,注意先看完所有题,不要只按顺序
一个很显然的想法是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;
}
第二题让我有些犯难,这个直接用程序搞似乎不是很容易。能不能手算呢?
从平面分割数想起,
加一条直线,最多可以增加6个区域
那么第二条似乎最多可以增加到7个区域了,那么答案是不是7+6+7+8+9+10呢?
想要验证但是时间不多,先填上就好。事实上,47就是正确的
又是一道比较难搞的题。
很快就放弃了手算。事实上,如果你足够老司机,那么就会想起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) (i−1,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]−j−k−l)/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;
}
学过自然语言处理的同学们都知道这个叫隐马尔可夫链,因为数据不大,我们仅仅采用贝叶斯公式就足够了
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(Q∣O)=∑qP(O∣q)P(q)P(Q)∗P(O∣Q)
分 子 = ( 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.6∗0.4∗0.7)∗(0.6∗0.4∗0.5)=0.168∗0.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
又是一道有点恼人的题目,当然你可以写一个不那么暴力的暴力,然后等待许久,比较保险。足够自信或者没有耐心的话,可以写正解,方法是二维前缀和。代码如下
#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书写,可能会发生超时。
参加蓝桥杯时,大题的实现,务必要多加小心,不可以有低级错误!
基本上是一道模拟裸题,不多叙述。
这一题题意有误,后来听说是前面三个数实际上应该三种建筑的耐久度为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)
实际上就是一道克鲁斯卡尔算法求生成树的问题,不同的是只需要做到剩下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)
应该算经典题目了吧。从起点对正图和反图各自求一次单源最短路,然后所有值相加即可。反图的单源最短路必然和正图的每个点到源的最短路对应
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)
这一题是一个典型的广搜。注意理解题意,题目中如果一个点有传送门,将会立即进行传送,而不能转而进行上下左右移动(当时理解错了,真坑)。于是,每一次四个方向扩展新状态,如果有传送门,我们需要把传送门尽头的那个点扔进队列,否则把自身扔进队列。在传送门中跳跃时不断判断是否已经到达终点。
实现中细节还是很多的,要理清思路再写。
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')
这一题是一道难题,基本上也符合蓝桥杯最后一题的定位。赛场上最后一题,不要浪费时间想着正解,赶紧把分水水到就行了。
回忆一下一维的情况。有箱子中1到n编号的小球,我们每次有放回地取,期望多少次能取到所有小球呢?
当我们首次取到x个小球时,下一个小球在x之外的概率为 ( n − x ) / n (n-x)/n (n−x)/n,因此,能取到新球的期望次数为 n / ( n − x ) n/(n-x) n/(n−x),因此,总期望等于 ∑ i = 0 n − 1 n / ( n − i ) \sum_{i=0}^{n-1} n/(n-i) ∑i=0n−1n/(n−i)
(附:如果这里每个小球取出的概率不同,则需要用到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/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];
其中左右都包含 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()