【五校联考5day1】登山

题目

描述

在这里插入图片描述

题目大意

给你一个 n ∗ n n*n nn的网格图。从 ( 0 , 0 ) (0,0) (0,0)开始,每次只可以向右或向上移动一格,并且不能越过对角线(即不能为 x &lt; y x&lt;y x<y)。
网格图上面有一些障碍,不能经过障碍。
问从 ( 0 , 0 ) (0,0) (0,0) ( n , n ) (n,n) (n,n)的方案数


思考历程

说实话这题没有怎么特别仔细地思考过。
因为后面两题比较水,于是我就先做后面两题。可是后面两题的码量都有点大,于是没有时间了。
剩下的一点点时间里面完成30分暴力。
叫我半个gjy。


正解

先想想最简单的情况: C = 0 C=0 C=0
许多人发现了这种情况下就是裸的卡特兰数,可是为什么我即使打表也没有发现?
还是推一下吧。
首先答案为不考虑是否经过对角线的所有方案减去经过对角线的所有方案。
前者比较显然,即为 C 2 n n C_{2n}^n C2nn。可以将右移看作 0 0 0,将上移看做 1 1 1,那么这就是一个有 n n n 0 0 0 n n n 1 1 1的排列。
然后就是如何求后者。
借用题解的图:
在这里插入图片描述
作出直线 y = x + 1 y=x+1 y=x+1,即图中粉色的线。
如果越过对角线,那么一定经过粉色的这条线。
在这里插入图片描述
(这张图里面已经有解析了)
对于一条经过粉色线的路径,选任意一个交点,将这个点到终点的路径关于粉色线对称。
容易发现其实经过粉线再到达终点的方案数等于到达终点对称点的方案数
终点对称点的坐标为 ( n − 1 , n + 1 ) (n-1,n+1) (n1,n+1),所以方案数为 C 2 n n − 1 C_{2n}^{n-1} C2nn1
用总数减去它即可。

现在考虑如何求 C = 1 C=1 C=1的情况。
很容易想到总数减去必须经过障碍的个数。
两点之间的方案数同样是作对称考虑,类似的。

看看这题 C C C最多为 1000 1000 1000,很小。
先将所有的点排序。
考虑DP,设 f i f_i fi表示从起点到达第 i i i个障碍,不经过其它障碍的方案数。
总数就是起点到它的方案数,然后枚举先前的点,求同时经过这两个点的方案数。
方程为 f i = d i s ( 0 , i ) − ∑ j = 1 i − 1 d i s ( j , i ) × f j f_i=dis(0,i)-\sum_{j=1}^{i-1}dis(j,i)\times f_j fi=dis(0,i)j=1i1dis(j,i)×fj
为了方便计算,可以设置第 n + 1 n+1 n+1个点,答案就是 f n + 1 f_{n+1} fn+1


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100000
#define maxC 1001
#define mo 1000000007
int n,c;
struct DOT{
	int x,y;
} d[maxC+2];
inline bool cmpd(const DOT &a,const DOT &b){
	return a.x<b.x || a.x==b.x && a.y<b.y;
}
long long fac[N*2+1],inv[N*2+1];
inline long long my_pow(long long x,int y){
	long long res=1;
	for (;y;x=x*x%mo,y>>=1)
		if (y&1)
			res=res*x%mo;
	return res;
}
inline long long C(int m,int n){
	if (n<0)
		return 0;
	return fac[m]*inv[n]%mo*inv[m-n]%mo;
}
inline long long calc(int u,int v){
	return (C((d[v].x-d[u].x)+(d[v].y-d[u].y),d[v].x-d[u].x)-C(((d[v].y-1)-d[u].x)+((d[v].x+1)-d[u].y),(d[v].y-1)-d[u].x)+mo)%mo;
}
long long f[maxC+2];
int main(){
//	freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&c);
	fac[0]=1,fac[1]=1;
	inv[0]=1,inv[1]=1;
	for (int i=2;i<=n*2;++i){
		fac[i]=fac[i-1]*i%mo;
		inv[i]=my_pow(fac[i],mo-2);
	}
	int tmp=0;
	for (int i=1;i<=c;++i){
		++tmp;
		scanf("%d%d",&d[tmp].x,&d[tmp].y);
		if (d[tmp].x<d[tmp].y)
			--tmp;
	}	
	c=tmp;
	d[++c]={n,n};
	sort(d+1,d+c+1,cmpd);
	for (int i=1;i<=c;++i){
		long long s=0;
		for (int j=1;j<i;++j)
			if (d[j].x<=d[i].x && d[j].y<=d[i].y)
				s=(s+f[j]*calc(j,i))%mo;
		long long t=calc(0,i);
		f[i]=(calc(0,i)-s+mo)%mo;
	}
	printf("%lld\n",f[c]);
	return 0;
}

总结

这是一种很经典的题型。
规定某些点不能走,问方案数。
那就设不能走的点被第一次到达,然后就能很好地解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值