一开始我去考虑每个空格接受的球,后来发现想复杂了。如果最上面定义为第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;
}