一开始我去考虑每个空格接受的球,后来发现想复杂了。如果最上面定义为第0行,最下面是第n-1行,那么我们可以手动补充第n行,也就是把最下面的每个格子都看成一个钉子。
这样就不用考虑空格了,只需考虑钉子的事情。
令dp[i][j]为走到第i行第j列的球数,sum为最后一行的总和,那么所求的答案就是dp[n][m]/sum
考虑每个点对下方的影响,即“我为人人”型:
这里采用基数的方法,假设球通过每个钉子后会自动裂开成左右两半,下方两边的钉子各接受一个,即钉子存在时,dp[i+1][j]+=dp[i][j],dp[i+1][j+1]+=dp[i][j]
当(i,j)处的钉子不存在时,(i,j)的球都会落到(i+2,j+1)(注意不是j)处,也就是球竖直下降了2层。另外,经过一层后1个球会裂开分成2个,穿过两层后1个球裂成的4个都落进了(i+2,j+1),即dp[i+2][j+1]+=dp[i][j]*4
如题目样例,上方红点没有钉子,上下两个红点坐标分别是(i,j)和(i+2,j+1),上方红点的球会全部落到下方红点
另外,如果有钉子时写成dp[i+1][j]+=dp[i][j]/2,dp[i+1][j+1]+=dp[i][j]/2,也就是对半分开,那无钉子就不用乘4了,不过那样赋初值就得dp[0][0]=(ll)((ll)1<<(ll)n),即最上面的钉子赋一个最大的数,最后答案是dp[n][m]/dp[0][0],注意转成long long
我这里假设一个球会裂成两半,初值就dp[0][0]=1即可
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll dp[52][52];
char a[52][52];
int n,m;
ll gcd(ll a,ll b){
if(b==0) return a;
else return gcd(b,a%b);
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >>m;
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
cin >> a[i][j];
}
}
dp[0][0]=1;
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
if(a[i][j]=='*'){
dp[i+1][j]+=dp[i][j];
dp[i+1][j+1]+=dp[i][j];
}
else if(a[i][j]=='.'){
dp[i+2][j+1]+=dp[i][j]*4;
}
}
}
// for(int i=0;i<=n;i++){
// for(int j=0;j<=i;j++){
// cout << dp[i][j] << " ";
// }
// cout << "\n";
// }
ll sum=0;
for(int j=0;j<=n;j++) sum+=dp[n][j];
ll g=gcd(dp[n][m],sum);
cout << dp[n][m]/g << "/" << sum/g << "\n";
return 0;
}
京公网安备 11010502036488号