BZOJ 3260. 跳
首先肯定要确定贪心走法,然后再考虑代价
首先注意到 $(x,y)$ 位置的值其实就是 $C(x+y,x)$ 的值
那么如果要从 $(0,0)$ 到 $(n,m)$,我们肯定不会往回走(不会跑出 $(n,m)$ 的矩形再绕回来)
归纳一下我们只要考虑往上和往右
不妨设 $m>n$
注意到边缘的代价比中间小得多,容易想到先绕着边缘走到 $(0,m)$,然后再直接往 $(n,m)$ 走
证明也可以考虑归纳法,首先显然到达 $(0,1)$,$(1,0)$ 我们来的方向是值比较小的方向,然后归纳得到从 $(n,m)$ 往回走最优路径就是每次都往值较小的位置走
所以配合一下自己画的图就证明了贪心是正确的
然后计算代价,走到 $(0,m-1)$ 代价都是 $1$ 很好计算
然后就是从 $(0,m)$ 一路往右走到 $(n,m)$,代价为 $\sum_{k=0}^{n}\binom{m+k}{k}$
展开一下就是 $\sum_{k=0}^{n} \frac {(m+k)!} {m!k!}$ 注意到 $((m+k)!)/(m!)$ 可以除掉,就变成了 $\prod_{k=m+1}^{m+k}k$
所以就是 $\sum_{k=0}^{n} \frac {\prod_{k=m+1}^{m+k}k} {k!}$
然后我看着这个式子愣了一下,好像没法再化了
突然发现题目保证 $nm<=10^{12}$,又因为 $n<m$ 了,所以 $n<=10^6$
然后动态维护一下当前 $\prod_{k=m+1}^{m+k}k$ 和 $k!$ 就做完了,记得模意义下要逆元,最后答案要加上走边缘的 $m$,别像我一样被胜利冲昏头脑...
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; typedef long long ll; inline ll read() { ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const ll mo=1e9+7; ll n,m,ans; ll ksm(ll x,ll y) { ll res=1; while(y) { if(y&1) res=res*x%mo; x=x*x%mo; y>>=1; } return res; } int main() { n=read(),m=read(); if(n>m) swap(n,m); ll sum1=1,sum2=1,now1=m%mo,now2=1; for(int i=0;i<=n;i++) { ans=((ans+sum1*ksm(sum2,mo-2)%mo)%mo+mo)%mo; now1++; now1%=mo; sum1=sum1*now1%mo; sum2=sum2*now2%mo; now2++; } printf("%lld\n",(ans+m)%mo); }