题目链接:http://codeforces.com/problemset/problem/722/E
题目大意:有一个n*m的网格,起点(1,1),终点(n,m),每次随机向右或向下走。一开始的能量为s,其中有k个网格异常,经过这样的网格会使能量从原来的x变为x/2的上取整(即(x+1)/2)。求最后到达(n,m)时的期望能量值。
数据范围:1 ≤ n, m ≤ 100 000, 0 ≤ k ≤ 2000, 1 ≤ s ≤ 1 000 000
题解:由于s每次遇到异常的网格都减半,最多减logs次就变成1,这样能量值最多有20种,所以我们只需要统计走到终点时每种能量值的方案数即可。
首先将点的坐标以x为第一关键字,y为第二关键字排序,然后在最前面添一个点(1,1)(即第0个点),从后往前进行dp(由于一开始想叉了写了从后往前的dp,这里也就依照代码进行分析,事实上从前往后也一样。)
为了方便,令d[i][j]表示从点 i 走到点 j 的方案数,即d[i][j]=C(p[j].x-p[i].x+p[j].y-p[i].y,p[j].x-p[i].x)。
设g[i][j]表示从点(n,m)走到第 i 个点,途中恰好经过了 j 个异常点(不包括点 i )的方案数。直接求g不太好求,那么我们再设f[i][j]表示经过了不超过j个点的方案数,这样只要在总方案数中减去超过了 j 个点的方案数即可。所以我们枚举经过的第 j 个异常点,则f[i][j]=d[i][(n,m)]-∑(g[l][j]*d[i][l]),g[i][j]=f[i][j]-f[i][j-1],其中p[i].x<=p[l].x且p[i].y<=p[l].y。
计算出所有的方案数之后,最后答案为(∑g[0][i]*a[i])/d[(1,1)][(n,m)],a[i]表示经过 i 个异常点后s的值。
时间复杂度O(n^2logs)
代码如下:
#include <algorithm>
#include <cstdio>
using namespace std;
const int mo=1000000007;
int a[30],fac[200005],ine[200005],g[2005][30];
struct P{
int x,y;
}b[2005];
bool operator <(P a,P b){
if (a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
int C(int x,int y){
return 1ll*fac[x]*ine[y]%mo*ine[x-y]%mo;
}
int qsm(int x,int y){
int i=1;
for (;y;y>>=1,x=1ll*x*x%mo)
if (y&1) i=1ll*i*x%mo;
return i;
}
int main(){
int n,m,k,s;
scanf("%d%d%d%d\n",&n,&m,&k,&s);
fac[0]=1;
for (int i=1;i<=n+m;i++) fac[i]=1ll*fac[i-1]*i%mo;
ine[n+m]=qsm(fac[n+m],mo-2);
for (int i=n+m;i>=1;i--) ine[i-1]=1ll*ine[i]*i%mo;
int len=0;a[0]=s;
for (len=1;s!=1;len++) a[len]=s=(s+1)/2;
len--;
for (int i=1;i<=k;i++) scanf("%d%d\n",&b[i].x,&b[i].y);
sort(b+1,b+k+1);b[0].x=b[0].y=1;
for (int i=k;i>=0;i--){
for (int j=0;j<len;j++){
g[i][j]=C(n-b[i].x+m-b[i].y,n-b[i].x);
for (int l=k;l>i;l--)
if (b[i].y<=b[l].y)
g[i][j]=(g[i][j]-1ll*g[l][j]*C(b[l].x-b[i].x+b[l].y-b[i].y,b[l].x-b[i].x))%mo;
}
g[i][len]=C(n-b[i].x+m-b[i].y,n-b[i].x);
for (int j=len;j>=1;j--) g[i][j]=(g[i][j]-g[i][j-1])%mo;
}
int ans=0;
for (int i=0;i<=len;i++)
ans=(ans+1ll*(g[0][i]+mo)*a[i])%mo;
ans=1ll*ans*qsm(C(n+m-2,n-1),mo-2)%mo;
printf("%d\n",ans);
}