AT4546 Grid 2

· · 题解

本题本质是对 dp 思想的一种优化。

我们发现,这个组合数来源于将该棋盘视作一个杨辉三角,然后 dp 方案数,推出的结论满足斜杨辉三角。

那么我们恢复这个思想,由于不能用的点比较少,我们按不能用的点走。先将所有不能用的点按 x 为第一关键字,y 为第二关键字排序。

接下来我们设 dp_{i} 表示从原点到 i 点不经过其他不能用的点的方案数。其中枚举的所有 i 点都是不能用的点(或终点),以下给出原理:

其实我们可以设 dp_{i} 是所有点的方案数,但这样时空都承受不了。

但我们会发现,不能用的点是很少的,于是我们仅需讨论不能用的点即可。

因为讨论到任何一个点的方案数,都只需要这个点之前不能用的点的方案数。所以这样做是正确的。

对所有 idp_{i} 的初值应当都是原点到 i 的方案数(即

而为了不经过其他的障碍点,我们需要排除前面一些障碍点的方案。 对于每个 $i$,我们仅需枚举所有 $i$ 之前的 $j$,使 $x_{i} \ge x_{j}$ , $y_{i} \ge y_{j}$。 然后可以得出状态转移方程 $dp_{i}=dp_{i}-dp_{j} \times C^{x_{i}-x_{j}+y_{i}-y_{j}}_{x_{i}-x_{j}}$。 这里算出的就是同时至少经过 $i$,$j$ 两点的方案数。 至于会不会重叠,由于我们按 $x$,$y$ 排过序,再考虑原先的 $dp_{j}$ 都是只经过 $j$ 这一个障碍物的,所以不会有重叠。 最后贴个代码,线性筛阶乘逆元 $+$ 组合数 dp。 ```cpp #include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll long long #define mode 1000000007 #define maxn 200000 using namespace std; ll inv[200005]; ll mul[200005]; void init() { inv[1]=1; inv[0]=1; mul[0]=1; for(int i=2;i<=maxn;i++) { inv[i]=(mode-mode/i)*inv[mode%i]%mode; } for(int i=1;i<=maxn;i++) { inv[i]*=inv[i-1]; inv[i]%=mode; mul[i]=mul[i-1]*i; mul[i]%=mode; } } int n,m,k; struct Bad { int x,y; }p[3005]; ll dp[3005]; bool cmp(Bad a,Bad b) { if(a.x==b.x) { return a.y<b.y; } return a.x<b.x; } ll C(int x,int y) { return mul[x]%mode*inv[y]%mode*inv[x-y]%mode; } int main() { // freopen("path.in","r",stdin); // freopen("path.out","w",stdout); scanf("%d%d%d",&n,&m,&k); init(); for(int i=1;i<=k;i++) { scanf("%d%d",&p[i].x,&p[i].y); } k++; p[k].x=n; p[k].y=m; sort(p+1,p+k+1,cmp); for(int i=1;i<=k;i++) { dp[i]=C(p[i].x+p[i].y-2,p[i].x-1); for(int j=1;j<i;j++) { if(p[j].x<=p[i].x&&p[j].y<=p[i].y) { dp[i]-=dp[j]*C(p[i].x-p[j].x+p[i].y-p[j].y,p[i].x-p[j].x)%mode; } } dp[i]=(dp[i]%mode+mode)%mode; } printf("%lld\n",dp[k]); } ```