6515. 【GDOI2020模拟03.28】数一数(one)

题目

(n)(m)列的格子,每列的格子中有一个是(1),其它的都是(0)
对于某一列,第(i)行是(1)的概率是(p_i)
从任意点出发,每次从当前列走到下一列的同一行、上一行,或下一行。
贡献是沿路的数字和。
(f(m))为最大值的期望。计算(lim_{m o 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
",ans);
	return 0;
}


总结

真是……
败在数学。

原文地址:https://www.cnblogs.com/jz-597/p/12608088.html