zoukankan      html  css  js  c++  java
  • 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;
    }
    
    

    总结

    真是……
    败在数学。

  • 相关阅读:
    48-最长不含重复字符的子字符串
    51-数组中的逆序对
    字符串的排列
    二叉树转链表
    求根
    构造二叉树
    二叉树中序遍历
    反转链表系列
    斐波那契系列
    f.lux
  • 原文地址:https://www.cnblogs.com/jz-597/p/12608088.html
Copyright © 2011-2022 走看看