6515. 【GDOI2020模拟03.28】数一数(one)
题目
有\(n\)行\(m\)列的格子,每列的格子中有一个是\(1\),其它的都是\(0\)。
对于某一列,第\(i\)行是\(1\)的概率是\(p_i\)。
从任意点出发,每次从当前列走到下一列的同一行、上一行,或下一行。
贡献是沿路的数字和。
\(f(m)\)为最大值的期望。计算\(\lim_{m \to \inf} \frac{f(m)}{m}\)
思考历程
话说题目描述是错了,按照题目描述来就根本过不了样例……
这个东西搞死了绝大多数人半天……
了解真正题意之后,发现\(n<=2\)时输出\(1\),于是就有分了。
正解
题目求的是“最大值”的“期望”,而不是“期望”的“最大值”,这一点上本来就已经很恶心了。考虑假如每个格子上的数都确定的情况下怎么搞。
这是个小学生dp问题:\(g_{i,j}\)表示到\((i,j)\)的最大贡献。
换一种更好看的记法:对于每一列,将最大值找出来,然后将所有的值变成自己和最大值的差值。
可以发现,差值的范围是\([0,n-1]\)。这样一列的状态最多只有\(n^n\)种。
用bfs将这些状态全部搜出来。搜出来发现状态种类数其实更少,不超过\(500\)。
建出一个图,每个点代表一个状态,边代表哪个状态有多少概率转移到哪些状态。
在\(m\)趋向于无限的时候,对于得到的第\(m\)列的状态,状态出现的概率是恒定的。
既然是恒定的,不妨对于每个状态,根据它的前驱状态列个方程。
不要忘了所有状态概率加起来为\(1\)。
集齐一堆方程之后解之,就可以知道每种状态出现的概率。
接下来考虑计算这些状态的贡献。
先说做法:对于某个状态,枚举下一列\(1\)的位置,然后看看这个状态能转移到下一列的\(1\)的位置中,有没有最大值。如果有,就计入贡献。
感性地解释一下:由于是极限,所以我们并不关心之前产生的最大值是多少。我们只需要关心它在到达无限列的时候,进行一次操作的增量。\(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;
}
总结
真是……
败在数学。