「CF722E Research Rover」

题目大意

给出一张 \(n\cdot m\) 的图,图上有 \(k\) 个特殊点,在初始点有一个贡献 \(t\) 每经过一个特殊点会导致贡献变为 \(\lceil\frac{t}{2}\rceil\).求到 \((n,m)\) 时的期望贡献.

分析

\(n,m\) 的范围都有 \(10^5\) 显然不能 \(\mathcal{O}(nm)\) 的 dp,考虑只会在特殊点时贡献才会改变,所以用 \(f_{i,j}\) 表示终点在第 \(i\) 个点,总共经过了 \(j\) 个点的方案数.这个东西看起来是 \(k^2\) 的,而且转移也需要 \(\mathcal{O}(k)\),时间复杂度 \(\mathcal{O}(k^3)\) 不能过.但是一个数经过了 \(\log\) 次除二向上取整后就会变成 \(1\),所以对于 \(f_{i,j}(\log_2t<j)\) 的方案是没有用的,所以时间复杂度就变成了 \(\mathcal{k^2\log_2t}\) 可以通过本题.

下面考虑如何转移 \(f_{i,j}\),因为在计算 \(x_i,y_i\) 这个点时需要把所以 \(x_j\leq x_i\land y_j\leq y_i\) 的点全部计算出来,所以要先对所有点按 \(x\) 的大小(\(y\) 的大小)排序.但是这个 \(f_{i,j}\) 的转移好像还是很麻烦,不如把 \(f_{i,j}\) 所代表的意义改成到第 \(i\) 个点时至少经过 \(j\) 个特殊点的方案数.那么转移方程就是:

\[f_{i,j}=\sum_{k\in[1,i]\land x_k\leq x_i\land y_k\leq y_i}C^{x_i+y_i-x_k-y_k+2}_{x_i-x_k+1}\cdot f_{k,j-1} \]

对于第 \(i\) 个特殊点经过恰好 \(j\) 次的方案数就是 \(f_{i,j}-f_{i,j+1}\).

然后会发现第一个样例就过不去.

在最后的贡献中 \((1,1)\to(2,1)\to(2,2)\to(2,3)\to(3,3)\) 会计算两次,但是如果 \(f_{k,j}\) 表示的是第 \(k\) 个点时恰好经过 \(j\) 个特殊点的方案数,那么是对的.所以需要在每次计算出至少的方案数后直接计算出恰好的方案数.

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=2005;
const int MAXNUM=1e6+5;
const long long MOD=1e9+7;
long long Pow(long long a,long long b,long long mod=MOD)
{
	long long result=1;
	while(b)
	{
		if(b&1)
		{
			result*=a;
			result%=mod;
		}
		a*=a;
		a%=mod;
		b/=2;
	}
	return result;
}
#define INV(a) Pow(a,MOD-2)
long long fac[MAXNUM],inv[MAXNUM];
void PreCom()
{
	fac[0]=1;
	REP(i,1,MAXNUM-1)
	{
		fac[i]=fac[i-1]*i%MOD;
	}
	inv[MAXNUM-1]=INV(fac[MAXNUM-1]);
	DOW(i,MAXNUM-2,0)
	{
		inv[i]=inv[i+1]*(i+1)%MOD;
	}
}
long long C(int n,int m)
{
	if(n<m)
	{
		return 0;
	}
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int n,m,k,s;
struct Point
{
	int x,y;
}point[MAXN];
bool Cmp(Point a,Point b)
{
	if(a.x==b.x)
	{
		return a.y<b.y;
	}
	return a.x<b.x;
}
long long f[MAXN][23];
int arr[23];
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&s);
	PreCom();//预处理组合数
	point[0].x=1;
	point[0].y=1;
	REP(i,1,k)
	{
		scanf("%d%d",&point[i].x,&point[i].y);
	}
	point[k+1].x=n;
	point[k+1].y=m;
	sort(point+1,point+1+k,Cmp);//按 x 排序
	f[0][0]=1;
	REP(i,1,k+1)
	{
		REP(j,0,i-1)
		{
			if(point[j].x<=point[i].x&&point[j].y<=point[i].y)//找到可以转移过来的点
			{
				int len=point[i].x-point[j].x+1;
				int wide=point[i].y-point[j].y+1;
				long long c=C(len+wide-2,len-1);//计算两点间的路径条数
				REP(p,1,22)
				{
					f[i][p]+=f[j][p-1]*c%MOD;//dp 至少经过 p 个特殊点的方案数
					f[i][p]%=MOD;
				}
			}
		}
		REP(p,1,22)
		{
			f[i][p]-=f[i][p+1];//将至少经过 p 个特殊点的方案数变为恰好经过 p 个特殊点的方案数
			f[i][p]+=MOD;
			f[i][p]%=MOD;
		}
	}
	arr[1]=s;
	REP(i,2,21)
	{
		arr[i]=(arr[i-1]+1)/2;
	}
	long long all=C(n+m-2,n-1);
	long long answer=0;
	REP(i,1,21)//计算经过小于等于 log 次的方案数的贡献
	{
		answer+=f[k+1][i]*arr[i];
		all-=f[k+1][i];
		all%=MOD;
		all+=MOD;
		all%=MOD;
		answer%=MOD;
	}
	answer+=all;
	answer%=MOD;
	printf("%lld\n",answer*INV(C(n+m-2,n-1))%MOD);//计算期望并输出
	return 0;
}
上一篇:Codeforces Round #677 (Div. 3)EF


下一篇:AtCoder Beginner Contest 171 F 另类题解