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