题目
有
n
n
n行
m
m
m列的格子,每列的格子中有一个是
1
1
1,其它的都是
0
0
0。
对于某一列,第
i
i
i行是
1
1
1的概率是
p
i
p_i
pi。
从任意点出发,每次从当前列走到下一列的同一行、上一行,或下一行。
贡献是沿路的数字和。
f
(
m
)
f(m)
f(m)为最大值的期望。计算
lim
m
→
inf
f
(
m
)
m
\lim_{m \to \inf} \frac{f(m)}{m}
limm→infmf(m)
思考历程
话说题目描述是错了,按照题目描述来就根本过不了样例……
这个东西搞死了绝大多数人半天……
了解真正题意之后,发现
n
<
=
2
n<=2
n<=2时输出
1
1
1,于是就有分了。
正解
题目求的是“最大值”的“期望”,而不是“期望”的“最大值”,这一点上本来就已经很恶心了。考虑假如每个格子上的数都确定的情况下怎么搞。
这是个小学生dp问题:
g
i
,
j
g_{i,j}
gi,j表示到
(
i
,
j
)
(i,j)
(i,j)的最大贡献。
换一种更好看的记法:对于每一列,将最大值找出来,然后将所有的值变成自己和最大值的差值。
可以发现,差值的范围是
[
0
,
n
−
1
]
[0,n-1]
[0,n−1]。这样一列的状态最多只有
n
n
n^n
nn种。
用bfs将这些状态全部搜出来。搜出来发现状态种类数其实更少,不超过
500
500
500。
建出一个图,每个点代表一个状态,边代表哪个状态有多少概率转移到哪些状态。
在
m
m
m趋向于无限的时候,对于得到的第
m
m
m列的状态,状态出现的概率是恒定的。
既然是恒定的,不妨对于每个状态,根据它的前驱状态列个方程。
不要忘了所有状态概率加起来为
1
1
1。
集齐一堆方程之后解之,就可以知道每种状态出现的概率。
接下来考虑计算这些状态的贡献。
先说做法:对于某个状态,枚举下一列
1
1
1的位置,然后看看这个状态能转移到下一列的
1
1
1的位置中,有没有最大值。如果有,就计入贡献。
感性地解释一下:由于是极限,所以我们并不关心之前产生的最大值是多少。我们只需要关心它在到达无限列的时候,进行一次操作的增量。
m
m
m趋向于无穷时,将
f
(
m
)
f(m)
f(m)看成一个类似于一次函数的东西,之前的最大值无论是多少只能看做是常数,但是下一步的增量可以看做一次项系数(到达无限的时候,增量不变),众所周知计算极限的时候只有最高次项是有意义的。
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 6
#define ll long long
#define mo 1000000007
inline ll qpow(ll x,int y=mo-2){
ll res=1;
for (;y;y>>=1,x=x*x%mo)
if (y&1)
res=res*x%mo;
return res;
}
int n;
int a[N];
int pown[N+1];
int id[46656],num,re[500];
int q[46656];
struct EDGE{
int to,p;
EDGE *las;
} e[500*6];
int ne;
EDGE *last[500];
inline void link(int u,int v,int p){
e[ne]={v,p,last[u]};
last[u]=e+ne++;
}
#define get(x,k) ((x)/pown[k]%n)
inline void init(){
int t[6];
int head=1,tail=1;
q[1]=0;
id[0]=++num;
re[num]=0;
while (head<=tail){
int x=q[head++];
for (int i=0;i<n;++i){
for (int j=0;j<n;++j){
int mn=get(x,j);
if (j-1>=0)
mn=min(mn,get(x,j-1));
if (j+1<n)
mn=min(mn,get(x,j+1));
t[j]=(mn-(i==j));
}
if (t[i]==-1)
for (int j=0;j<n;++j)
t[j]++;
int s=0;
for (int j=0;j<n;++j)
s+=pown[j]*t[j];
if (!id[s]){
id[s]=++num;
re[num]=s;
q[++tail]=s;
}
link(id[s],id[x],a[i]);
}
}
}
int eql[500][500],cnt;
int p[500];
void calc(){
for (int i=1;i<=num;++i){
int j=i;
for (;j<=num && eql[i][j]==0;++j);
if (i!=j)
for (int k=0;k<=num;++k)
swap(eql[i][k],eql[j][k]);
ll invi=qpow(eql[i][i]);
for (int k=0;k<=num;++k)
eql[i][k]=(eql[i][k]*invi)%mo;
for (++j;j<=num;++j){
ll c=eql[j][i];
for (int k=0;k<=num;++k)
eql[j][k]=((eql[j][k]-c*eql[i][k])%mo+mo)%mo;
}
}
for (int i=num;i>=1;--i){
ll s=eql[i][0];
for (int j=i+1;j<=num;++j)
s=((s-(ll)eql[i][j]*p[j])%mo+mo)%mo;
p[i]=s;
}
}
int main(){
freopen("one.in","r",stdin);
freopen("one.out","w",stdout);
scanf("%d",&n);
pown[0]=1;
for (int i=1;i<=n;++i)
pown[i]=pown[i-1]*n;
for (int i=0;i<n;++i){
double x;
scanf("%lf",&x);
a[i]=int(x*10)*qpow(10)%mo;
}
init();
++cnt;
eql[cnt][0]=1;
for (int i=1;i<=num;++i)
eql[cnt][i]=1;
for (int i=2;i<=num;++i){
++cnt;
eql[cnt][i]=mo-1;
for (EDGE *ei=last[i];ei;ei=ei->las)
(eql[cnt][ei->to]+=ei->p)%=mo;
}
calc();
ll ans=0;
for (int i=1;i<=num;++i){
ll s=0;
for (int j=0;j<n;++j)
if (get(re[i],j)==0 || (j-1>=0 && get(re[i],j-1)==0) || (j+1<n && get(re[i],j+1)==0))
s+=a[j];
s%=mo;
ans=(ans+s*p[i])%mo;
}
printf("%lld\n",ans);
return 0;
}
总结
真是……
败在数学。