NKOJ 4249 打气球
问题描述
周末何老板到磁器口游玩。街边有小贩在组织一种打气球游戏,何老板很感兴趣。
店家立了一块布,布上画了N*N的方格,有的方格里挂上了气球,有的没有。
游戏规则如下:第1步.观察。如果每一行都至少有一个方格没有气球,同时每一列都至少有一个方格没有气球,游戏结束。否则进行第2步。
第2步.抛骰子。店家拿出一个特制的骰子,该骰子有N个面,上面依次有1到N这N个数字。玩家先后抛两次骰子,设第一次抛出的数字为x,设第二次抛出的为y (注:抛出的数字是随机的)。
第3步.打气球。若坐标为(x,y)的格子里有气球,玩家必须将其打爆。子弹1块钱一发。如果该格子没有气球,忽略该格子,玩家不用开枪,但玩家也需要支付给店家1块钱。
第4步.继续。执行第1步。何老板是个神枪手,他能做到百发百中。他想你帮他算算,对于当前给出的这局游戏,预计要花多少钱才能结束。
输入格式
第一行,两个整数n和m,n表示方格的尺寸,m表示游戏开始时,有m个格子里是没有气球的。
接下来m行,每行两个整数x,y,表示坐标为x,y的格子里没有气球。
输出格式
一行,一个实数,完成游戏预计花费,保留2个小数位。
样例输入 1
5 2
2 3
4 1
样例输出 1
11.77
样例输入 2
2 2
1 1
1 2
样例输出 2
2.00
样例输入 3
1 1
1 1
样例输出 3
0.00
数据范围
1 ≤ n ≤ 2000
0 ≤ m ≤ min(n^2, 20000)
1≤x,y≤n
显然是数学期望。但是由于没定出状态导致考试时爆零了。其实现在看来状态也比较显然。
为了求数学期望,我们要想办法表示一些状态,并推导出状态的转移。这道题使用整个方格作为状态显然是不现实的。考虑应当使用怎样的状态。
既然题目中的终止状态是每行每列都至少有一个空位,那么不妨考虑状态 f[i][j] 表示有i行没有空位,j列没有空位时的数学期望。那么终止状态就是 f[0][0] 。考虑 f[i][j] 的递推方程。
图中红黄蓝三种颜色表示还有气球。注意到每次选到的坐标实际上有四种情形:
(1)不会造成状态转移(白)
(2)会使满行的数目减少一(黄)
(3)会使满列的数目减少一(绿)
(4)同时使满行、满列的数目减少一(红)
那么容易计算出坐标为每种情况的概率,这样就可以得到下面的式子:
f[i][j]=N2−[(i+j)N−ij]N2f[i][j]+jN−ijN2f[i][j−1]+iN−ijN2f[i−1][j]+ijN2f[i][j]+1
移项后整理得:
f[i][j]=(jN−ij)f[i][j−1]+(iN−ij)f[i−1][j]+ijf[i][j]+N2(i+j)N−ij
可以直接用两个循环解决,下面的代码中采用的是记忆化搜索。
#include<stdio.h>
#define MAXN 2005
int N,M,R,C;
bool row[MAXN],col[MAXN];
double w[MAXN][MAXN];
void DFS(int c,int r)
{
if(w[c][r]!=-1)return;
if(c==0&&r==0){w[c][r]=0;return;}
double ans=0;
if(c)DFS(c-1,r),ans+=(c*N-c*r)*w[c-1][r];
if(r)DFS(c,r-1),ans+=(r*N-c*r)*w[c][r-1];
if(c&&r)DFS(c-1,r-1),ans+=c*r*w[c-1][r-1];
w[c][r]=1.0*(ans+N*N)/((c+r)*N-c*r);
}
int main()
{
int i,j,x,y;
scanf("%d%d",&N,&M);
for(i=1;i<=M;i++)
{
scanf("%d%d",&x,&y);
col[x]=true;
row[y]=true;
}
for(i=1;i<=N;i++)if(!col[i])C++;
for(i=1;i<=N;i++)if(!row[i])R++;
for(i=0;i<=N;i++)
for(j=0;j<=N;j++)w[i][j]=-1;
DFS(C,R);
printf("%.2lf",w[C][R]);
}