Intel Code Challenge Elimination Round (Div.1 + Div.2, combined) E

题意:

一艘飞往火星的飞船,,,,可以看成它现在在一个n*m的网格里,现在在位置(1,1),目标是位置(n,m),它会随机选一条到达(n,m)的最短路出发,这个网格有k个特别的点,这些点的道路崎岖坎坷--以至于当飞船经过这些点的时候,会消耗一半的电量,初始时飞船电量为s,设当前剩余电量为x,经过一个特别点,电量变成,询问从起点到终点路径上的期望剩余电量,答案mod 1E9 + 7输出


solution:

ans是个分数P/Q的形式,P是所有方案的剩余电量之和,Q是方案数。因为每次经过特殊点剩余电量/2,因此,经过特殊点次数超过20次的,剩余电量总是1。。现在,如果能统计出少于20次的各种方案,答案也就解决了

从(r1,c1)到(r2,c2),方案数是C(r2 - r1 + c2 - c1,c2 - c1)证明用隔板法,记作path(r1,c1,r2,c2)

将所有特殊点按照两个坐标升序排序,这样第i个点可能到达的点一定是属于(i,k]

考虑第i个点到达终点,不经过其它点的方案数,记作Fi

Fi = path(ri,ci,n,m) - ∑path(ri,ci,rj,cj)*Fj  (j∈(i,k])

这样每条多余的路只会被减去一遍,当且仅当j是这条路的最后一个点

令Gi,v:从第i个点出发,到达终点,除了第i个点外,经过v个点的方案数

Gi,0 = Fi

Gi,v = path(ri,ci,n,m) - ∑Gi,j (j∈[0,v)) - ∑path(ri,ci,rj,cj)*Gj,v(j∈(i,k])

这样长度大于v的路径只会被减一次,证明同上,长度小于v的路径,显然只会被减一次

将(1,1)也考虑成关键点,用上述公式递推之即可


#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<bitset>
#include<algorithm>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<cmath>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int mo = 1E9 + 7;
const int maxn = 2E5 + 20;

struct Point{
	int r,c;
	Point(){}
	Point(int r,int c): r(r),c(c){}
	bool operator < (const Point &b) const {
		if (r < b.r) return 1;
		if (r > b.r) return 0;
		return c < b.c;	
	}
}p[maxn];

int n,m,k,s,ans,fac[maxn],inv[maxn],f[maxn],g[maxn][22];

int path(int r1,int c1,int r2,int c2)
{
	int x = r2 - r1;
	int y = c2 - c1;
	if (x < 0 || y < 0) return 0;
	return 1LL*fac[x+y]*inv[y]%mo*inv[x]%mo;
}

int ksm(int x,int y)
{
	int ret = 1;
	for (; y; y >>= 1) {
		if (y&1) ret = 1LL*ret*x%mo;
		x = 1LL*x*x%mo;
	}
	return ret;
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n >> m >> k >> s;
	for (int i = 1; i <= k; i++) {
		int x,y; 
		scanf("%d%d",&x,&y);
		p[i] = Point(x,y);
	}
	fac[0] = 1;
	for (int i = 1; i <= n + m; i++)
		fac[i] = 1LL*i*fac[i-1]%mo;
	inv[n+m] = ksm(fac[n+m],mo-2);
	for (int i = n + m - 1; i >= 0; i--)
		inv[i] = 1LL*(i+1)*inv[i+1]%mo;
	p[++k] = Point(1,1);
	sort(p + 1,p + k + 1);
	for (int i = k; i; i--) {
		int T = path(p[i].r,p[i].c,n,m);
		f[i] = T;
		for (int j = i + 1; j <= k; j++) {
			f[i] -= 1LL*path(p[i].r,p[i].c,p[j].r,p[j].c)*f[j]%mo;
			f[i] = (f[i] + mo) % mo;
		}
		g[i][0] = f[i];
		for (int v = 1; v <= 20; v++) {
			g[i][v] = T;
			for (int j = i + 1; j <= k; j++) {
				g[i][v] -= 1LL*path(p[i].r,p[i].c,p[j].r,p[j].c)*g[j][v]%mo;
				g[i][v] = (g[i][v] + mo) % mo;
			}
			for (int j = 0; j < v; j++) 
				g[i][v] = (g[i][v] - g[i][j] + mo) % mo;
		}
	}
	
	int tot = path(1,1,n,m);
	for (int v = 0; v <= 20; v++) {
		ans = (ans + 1LL*s*g[1][v]%mo) % mo;
		s = (s&1)?(s>>1)+1:s>>1;
		tot -= g[1][v];
		tot = (tot + mo) % mo;
	}
	ans = (ans + 1LL*s*tot%mo) % mo;
	ans = 1LL*ans*ksm(path(1,1,n,m),mo-2)%mo;
	cout << ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值