【JLOI2015】骗我呢
题意简述:
求原点到点$P(n+m+1,n)$,并且不能经过(或跨越)直线$A:y=x+1$和$B:y=x-(m+2)$的路径条数。
题解:
这道题应该可以算是格路计算问题的巅峰了吧。
首先,我们把$P$点沿$A$进行翻折,记为$P'$,从原点到$P'$的路径条数就是“从原点到$P$,一定经过(或跨越)直线$A$的路径条数”
注意我们不知道是什么时候经过的,也不知道前后还有没有别的经过事件,因为我们只考虑了直线$A$。
我们把$P'$沿$B$翻折,记为$P''$,考虑从原点到$P''$的路径条数是什么意思:
由上面一个问题,我们很自然的猜想,“是不是从原点到$P$,一定经过(或跨越)直线$A$和$B$的路径条数呢?”
但是我们很快可以发现这个猜想是错误的,因为如果是这样的话,由对称性可知,先沿$A$折,再沿$B$折,和先沿$B$折,再沿$A$折,算出来的东西是一样的。
进一步的,即$A$和$B$关于直线$y=x$对称,
但其实,并没有保证这个条件。
那我们继续很自然的猜想,“是不是从原点到$P$,先经过$B$再经过$A$的路径条数呢”(注意这里我可能没有讲清楚,是保证有两次经过,并且有先$A$再$B$),
发现就是这样,因为我们可以把第一次经过$B$的部分沿$B$翻折,从第一个交点处到$P'$,一定要跨越$A$,所以是先$B$后$A$。
我们发现还是可以反复经过$B$,再经过$A$的。那么我们就把多次连续的经过一条直线记做一个事件,即把经过直线状态的改变看做一个事件。
那所有的不合法方案就是形如$A$,$B$,$AB$,$BA$,$ABA...$,共同点是$AB$交替出现,不知道先$A$还是先$B$,也不知道什么时候结束。
我们可以看成有一个文本检索器,每次可以搜一个子序列,查找文本中出现了此子序列的子段个数(更严谨的,查找文本中出现了此子序列的子段加权的个数和),怎么容斥的把所有不合法方案算出来。
先减去$A$开头的,再减去$B$开头的就可以了。
如何算$A$开头的?我们先沿$A$翻折,查$A$,算出来是多的,因为还有$B$开头且含$A$的,再把这一部分减去,即沿$B$翻折,查$BA$,减去$BA$的,但是会有多减的,因为还有$ABA$开头的,就再加上,发现加多了,因为还有开头$BABA$的......下一次的永远比上一次少,也就是它肯定会少到$0$,就在那里停止就可以了。
算$B$开头的同理。
## update:2020 - 5 - 12
听神仙 `aysn` 讲了这个问题,想到自己之前做过,用现在的方法重新推一遍。
重新写一下这个问题,即从 $(0,0) \rightarrow (n,n)$,不能跨越(但是可以经过)直线 $y=x$ 和 $y=x-m$,求方案数。
对于 $(0,0)$ 到 $(n,n)$ 的一条路径 $P$,定义一个字符串 $S$,设它在行进过程中,如果经过了直线 $l_1$,就在 $S$ 后面加一个 $1$,如果它经过了 $l_2$,就在 $S$ 后面加一个 $2$,这样形成的字符串 $S$,我们规定它为 $w(P)$,但是我们发现这样没法做,所以把这个字符串连续的一段 $1$ 和 $2$ 缩成一个 $1/2$。设 $P'$ 为 $P$ 的一个子串。
$$\begin{align*}ans&=\sum_P [w(P)=""]\\&=\sum_P \sum_{P' \subs P} (-1)^{|P|}=\sum_{P'}(-1)^{|P'|}\sum_{P}|P'\subset P| \end{align*}$$
对于 $P'\ subset P$ 的所有路径 $P$,我们只需要按照 $P'$ 翻着 $(n,n)$,得到的点 $(x,y)(x+y=n+n)$,即为符合条件的 $P$ 的个数。
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define mod 1000000007 #define N 3000300 #define LL long long #define ri register int void add(int &x,int y) { x+=y; if (x>=mod) x-=mod; } int mul(int a,int b) { LL c=a*1LL*b; return c%mod; } int n,m,inv[N],jc[N],jv[N],maxn,ans; int c(int x,int y) { if (x<0 || y<0) return 0; return mul(mul(jc[x+y],jv[x]),jv[y]); } void flip1(int &x,int &y) { swap(x,y); x-=1; y+=1; } void flip2(int &x,int &y) { swap(x,y); x+=m+2; y-=m+2; } int main() { cin>>n>>m; inv[0]=inv[1]=jc[0]=jv[0]=1; maxn=max(n,m)*3+1; for (ri i=2;i<=maxn;i++) inv[i]=mul(inv[mod%i],(mod-mod/i)); for (ri i=1;i<=maxn;i++) jc[i]=mul(jc[i-1],i); for (ri i=1;i<=maxn;i++) jv[i]=mul(jv[i-1],inv[i]); int x=n+m+1,y=n; ans=c(x,y); while (x>=0 && y>=0) { flip1(x,y); add(ans,mod-c(x,y)); flip2(x,y); add(ans,c(x,y)); } x=n+m+1,y=n; while (x>=0 && y>=0) { flip2(x,y); add(ans,mod-c(x,y)); flip1(x,y); add(ans,c(x,y)); } cout<<ans<<endl; return 0; }