P3266 [JLOI2015]骗我呢 [* hard]
给定 (n imes m) 的棋盘,规定左下角为 ((1,1)),右上角为 ((n,m)),对于棋盘内的每个元素:
- (a_{i,j}in [0,m])
- (a_{i,j}<a_{i,j+1})
- (a_{i,j}<a_{i-1,j+1})
即,每行满足从左往右依次递增,每个对角线满足从左上到右下依次递增的棋盘数量。
(n,mle 10^6)
Solution
观察发现 (a_{i,j}in [j-1,j]),同时如果 (j) 使得 (a_j=j),那么其后均需满足此条件。
观察发现对于对角线,如果 (a_j=j),那么其往后必然也如此。
于是假设一个位置是最后一个 (a_j=j-1),那么其往前均如此,往后均非如此。
同时对于每个对角线,也有其往上如此,往下非如此。
设 (f_i) 表示对于第 (i) 行最后一个 (j) 使得 (a_{i,j}=j-1),此时 (f_iin [0,m]) 这样我们只需要考虑对角线的限制:
不难发现若 (f_{i-1}-1>f_i),那么就有:对于 ((i-1,f_{i-1})) 和 ((i,f_{i-1}-1)) 这两个值相同,均为 (f_{i-1}-1)
我们等价于对 (f) 序列计数,一个 (f) 序列合法当且仅当对于 (i),有 (f_ige f_{i-1}-1)
这样我们可以得到一个容易的 dp 来计算答案,复杂度 (mathcal O(nm))
接下来,我们实际上需要计数的序列 (a) 的数量,满足:
- (a_iin [0,m])
- (a_i+1ge a_{i-1})
这个第二个限制比较难受,我们考虑令 (b_i=a_i+i),那么不难发现限制等价于 (b_ige b_{i-1})
我们考虑对 (b_i) 序列进行计数,这样 (b_iin [i,i+m])
现在考虑网格上的路径计数问题,条件可以等价于:
从 ((0,0)) 出发,在不经过 (y=x-1) 和 (y=x+m+2) 的情况下到达 ((n,n+m)) 的方案数。
这样将第一次到达 (x=k) 时的 (y) 坐标写下来就得到了 (b_k)
考虑容斥,我们计算:
路径总数,减去经过了 (y=x-1) 的路径数,减去经过 (y=x+m+2) 的路径数,加上经过了 (y=x-1) 和 (y=x+m+2) 的路径数。
前三种都是平凡的,考虑最后一种如何处理,类似的,我们考虑对称,将 ((0,0)) 关于 (y=x-1) 进行对称,将 ((n,n+m)) 关于 (y=x+m+2) 进行对称,这样从两个对称点走到另一个对称点的方案即为答案。(这是我一开始 naive 的想法)
然而有 Hack,(n=3,m=0)((nge m+3) 的时候都是 Hack 数据)
这是由于进行对称之后,我们不难发现我们计算的路径一定都是先经过了下面的限制线,然后到达终点时要经过上面的限制线的情况。
然而如果 (nle m+2) 的话,那么即 ((0,0) o (n,n+m))
假设先到达了第一条限制线,那么此时下标至少为 ((0,m+2)),这个时候要到达第二条限制线,相当于要到达 ((m+3,m+2)),由于 (m+3>n),所以肯定不会产生此类情况。
假设到达 ((n,n+m)) 时是最后从 (y=x-1) 处出来的,那么由于之前要经过 (y=x+m+2),然而由于经过了 (y=x+m+2) 是绝对无法再经过 (y=x-1) 的,所以最后经过的线也必须是 (y=x+m+2) 。
设两条限制线分别为 A 和 B,假设我们依次经过了 A 和 B 或者说达成了 AAABBAA... 这种循环的模式
我们希望于让这样的走法只被加上恰好一次。
考虑开头是 A 的串,我们现在希望计算所有开头是 A 的串。
然而如果执行对称,我们没有办法保证其之前没有经过 B
那么,容斥,我们减去之前经过了 B 的方案。
换而言之我们减去 BA 类型的串。
但是 B 也无法真的就是开头,所以我们加上 ABA 类型的串...
于是我们得到了一个容斥的做法,对以 B 开头重复一次流程即可。
不难发现长度为奇数的对答案的贡献都是 -1,偶数都是 1,所以直接减去 A 和 B,加上 AB 和 BA,减去 ABA 和 BAB 就可以了。
这样可以直接多次对称,是十分方便的(非常正确)。
现在考虑对称,根据观察,我们发现 ((x_0,y_0)) 关于 (y=x+b) 对称的结果为 ((y_0-b,x_0+b))
那么不难注意到,每次关于 (y=x-1) 对称,几乎只是将 (x,y) 进行了交换。
每次将其关于 (y=x+m+2) 对称,会使得其中 (x) 增大 (m),然后交换又会回来,换而言之每操作两次会使得 (x,y) 中的一个增大 (m)
于是交换次数上界为 (frac{n}{m}),所以我们的复杂度也是 (mathcal O(frac{n}{m}))
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int P = 1e9 + 7 ;
const int N = 5e6 ;
struct node {
int x, y ;
node(int _x = 0, int _y = 0) { x = _x, y = _y ; }
} Ed ;
int fpow(int x, int k) {
int ans = 1, base = x ;
while(k) {
if(k & 1) ans = 1ll * ans * base % P ;
base = 1ll * base * base % P, k >>= 1 ;
} return ans ;
}
int n, m, maxn, fac[N + 5], inv[N + 5] ;
int C(int x, int y) {
if( x < 0 || y < 0 || x < y ) return 0 ;
return fac[x] * inv[y] % P * inv[x - y] % P ;
}
int operator - (node a, node b) {
int dx = a.x - b.x, dy = a.y - b.y ;
return C(dx + dy, dx) ;
}
node F(node x, int b) {return node(x.y - b, x.x + b) ;}
int Get(node x, int type) {
if( (Ed - x) == 0 ) return 0 ;
int ans = ((Ed - x) - Get(F(x, (type == 1) ? m + 2 : -1), type ^ 1) + P) % P ;
return ans ;
}
signed main()
{
n = gi(), m = gi() ; Ed = node(n, n + m) ;
maxn = 2 * n + m ;
fac[0] = inv[0] = 1 ;
rep( i, 1, N ) fac[i] = fac[i - 1] * i % P ;
inv[N] = fpow( fac[N], P - 2 ) ;
drep( i, 1, N ) inv[i - 1] = inv[i] * i % P ;
int ans = C(maxn, n) ;
ans -= Get(F(node(0, 0), -1), 1) ;
ans -= Get(F(node(0, 0), m + 2), 0) ;
ans = (ans % P + P) % P ;
cout << ans << endl ;
return 0 ;
}