题目大意
像走象棋,棋盘有两行,很多列,第一列上有
n
n
n 个炮, 第二列上有
m
m
m 个炮,炮的规则就和象棋一样,可以隔一个打一个。现在有两种规则,对于每一种规则你需要回答一个数。
规则一:
你可以任意地下象棋,定义
f
i
f_i
fi 表示你下
i
i
i 步之后可能形成的局面
求
f
0
x
o
r
f
1
x
o
r
…
x
o
r
f
n
+
m
−
4
f_0 \ xor \ f_1 \ xor \ … \ xor f_{n+m-4}
f0 xor f1 xor … xorfn+m−4 的值
规则二:
你在下象棋的时候下过第二行之后就不能下第一行了,同样求
f
0
x
o
r
f
1
x
o
r
…
x
o
r
f
n
+
m
−
4
f_0 \ xor \ f_1 \ xor \ … \ xor f_{n+m-4}
f0 xor f1 xor … xorfn+m−4 的值
解题思路
我们首先将
n
=
n
−
2
m
=
m
−
2
n = n-2 \ \ \ m = m-2
n=n−2 m=m−2
在一行有
n
n
n 个炮进行一步操作的局面有
2
×
(
n
−
2
)
2\times (n-2)
2×(n−2) 种,可以这样考虑有
n
−
2
n-2
n−2 个炮可以向右移动,有
n
−
2
n-2
n−2个炮可以向左移动。
所以我们在一行有
n
n
n 个炮进行
k
k
k 步操作的结果有
2
k
×
C
n
k
×
A
k
k
2^k \times C^k_n \times A^k_k
2k×Cnk×Akk
规则一
所以对于第一种操作,我们进行
k
k
k 次操作可能形成的局面有
∑
i
=
0
k
C
k
i
×
2
i
×
C
n
i
×
A
i
i
×
2
k
−
i
×
C
m
k
−
i
×
A
k
−
i
k
−
i
\sum_{i=0}^k C_k^i\times2^i\times C^i_n\times A_i^i\times2^{k-i}\times C^{k-i}_m \times A_{k-i}^{k-i}
i=0∑kCki×2i×Cni×Aii×2k−i×Cmk−i×Ak−ik−i
化简一下
2
k
×
k
!
×
∑
i
=
0
k
×
C
n
i
×
C
m
k
−
i
2^k \times k! \times \sum_{i=0}^k\times C_n^{i} \times C_m^{k-i}
2k×k!×i=0∑k×Cni×Cmk−i
由组合数的意义,我们化简一下求和式
2
k
×
k
!
×
C
n
+
m
k
2^k \times k! \times C_{n+m}^{k}
2k×k!×Cn+mk
所以我们对于规则一的答案就是
对于
k
∈
[
0
,
m
+
n
]
k \in [0, m+n]
k∈[0,m+n] 将 上式相异或
规则二
进行
k
k
k 此操作可以形成的局面是
∑
i
=
0
k
2
i
×
C
n
i
×
A
i
i
×
2
k
−
i
×
C
m
k
−
i
×
A
k
−
i
k
−
i
\sum_{i=0}^k 2^i\times C^i_n\times A_i^i\times2^{k-i}\times C^{k-i}_m \times A_{k-i}^{k-i}
i=0∑k2i×Cni×Aii×2k−i×Cmk−i×Ak−ik−i
化简一下
2
k
∑
i
=
0
k
n
!
(
n
−
i
)
!
×
m
!
(
m
−
k
+
i
)
!
2^k \sum_{i=0}^k \frac{n!}{(n-i)!}\times\frac{m!}{(m-k+i)!}
2ki=0∑k(n−i)!n!×(m−k+i)!m!
我们上下同时乘以
(
m
+
n
−
k
)
!
(m+n-k)!
(m+n−k)!得到
2
k
×
n
!
×
m
!
(
n
+
m
−
k
)
!
∑
i
=
0
k
C
n
+
m
−
k
n
−
i
2^k \times \frac{n! \times m!}{(n+m-k)!} \sum_{i=0}^k C_{n+m-k}^{n-i}
2k×(n+m−k)!n!×m!i=0∑kCn+m−kn−i
为了后面计算方便,我们将求和式转化一下
2
k
×
n
!
×
m
!
(
n
+
m
−
k
)
!
∑
i
=
n
−
k
n
C
n
+
m
−
k
i
2^k \times \frac{n! \times m!}{(n+m-k)!} \sum_{i=n-k}^n C_{n+m-k}^{i}
2k×(n+m−k)!n!×m!i=n−k∑nCn+m−ki
我们发现,求和式是一个组合数的前缀和之差
定义
S
(
n
,
m
)
=
∑
i
=
0
m
C
n
i
S(n, m) = \sum_{i=0}^mC_n^i
S(n,m)=i=0∑mCni
用如下公式来求前缀和
S
(
n
,
m
+
1
)
=
S
(
n
,
m
)
+
C
n
m
+
1
S(n,m+1) = S(n,m) + C_n^{m+1}
S(n,m+1)=S(n,m)+Cnm+1
S
(
n
,
m
−
1
)
=
S
(
n
,
m
)
−
C
n
m
S(n, m-1) = S(n, m) - C_n^m
S(n,m−1)=S(n,m)−Cnm
S
(
n
+
1
,
m
)
=
2
S
(
n
,
m
)
−
C
n
m
S(n+1, m) = 2S(n,m) - C_n^m
S(n+1,m)=2S(n,m)−Cnm
我们将求和式转化一下
∑
i
=
n
−
k
n
C
n
+
m
−
k
i
=
∑
i
=
0
n
C
n
+
m
−
k
i
−
∑
i
=
0
n
−
k
−
1
C
n
+
m
−
k
i
\sum_{i=n-k}^n C_{n+m-k}^{i} = \sum_{i=0}^n C_{n+m-k}^{i} - \sum_{i=0}^{n-k-1} C_{n+m-k}^{i}
i=n−k∑nCn+m−ki=i=0∑nCn+m−ki−i=0∑n−k−1Cn+m−ki
我们在计算的时候开一个数组
s
1
[
k
]
s1[k]
s1[k] 表示
∑
i
=
0
n
C
n
+
m
−
k
i
\sum_{i=0}^n C_{n+m-k}^{i}
∑i=0nCn+m−ki
显然
s
1
[
n
+
m
]
=
1
s1[n+m] = 1
s1[n+m]=1,我们可以这样逆向递推
s
1
[
i
−
1
]
=
2
×
s
1
[
i
]
−
C
n
+
m
−
i
n
s1[i-1] = 2\times s1[i] - C_{n+m-i}^n
s1[i−1]=2×s1[i]−Cn+m−in
同时开另外一个数组
s
2
[
k
]
s2[k]
s2[k] 表示
∑
i
=
0
n
−
k
−
1
C
n
+
m
−
k
i
\sum_{i=0}^{n-k-1} C_{n+m-k}^{i}
∑i=0n−k−1Cn+m−ki
显然
s
2
[
n
−
1
]
=
1
s2[n-1] = 1
s2[n−1]=1,我们可以这样逆向递推
s
2
[
i
−
1
]
=
2
×
s
2
[
i
]
+
C
n
+
m
−
i
n
−
i
s2[i-1] = 2\times s2[i] + C_{n+m-i}^{n-i}
s2[i−1]=2×s2[i]+Cn+m−in−i
然后我们将
s
1
s1
s1 和
s
2
s2
s2 作差就能得到我们想要的求和式了
至此,第二步也结束了。
Code
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 1e7 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 9;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int f[MAXN];
int f2[MAXN];
int finv[MAXN];
int s1[MAXN], s2[MAXN];
ll n, m;
ll C(ll n, ll m){
if(m > n)
return 0;
return 1ll * f[n] * finv[n-m] % mod * finv[m] % mod;
}
void solve(){
cin >> n >> m;
n -= 2;
m -= 2;
s1[n+m] = 1;
for(int i = n+m; i > 0; --i){
s1[i-1] = (2ll * s1[i] % mod - C(n+m-i, n) + mod) % mod;
}
s2[n-1] = 1;
for(int i = n-1; i > 0; --i){
s2[i-1] = (2ll * s2[i] % mod + C(n + m - i, n - i)) % mod;
}
for(int i = 0; i <= n+m; ++i)
s1[i] = (1ll * s1[i] - s2[i] + mod) % mod;
ll ans1 = 0, ans2 = 0;
for(int i = 0; i <= n+m; ++i){
ans1 ^= 1ll * f2[i] * f[i] % mod * C(n+m, i) % mod;
ans2 ^= 1ll * f2[i] * s1[i] % mod * f[n] % mod * f[m] % mod * finv[n+m-i] % mod;
}
cout << ans1 << " " << ans2 << endl;
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
finv[1] = 1;
finv[0] = 1;
for(int i = 2; i < MAXN; ++i)
finv[i] = 1ll * (mod - mod / i ) * 1ll * finv[mod % i] % mod;
f[0] = 1;
for(ll i = 1; i < MAXN; i++){
f[i] = f[i-1] * i % mod;
finv[i] = 1ll * finv[i] * finv[i-1] % mod;
}
f2[0] = 1;
for(ll i = 1; i < MAXN; i++)
f2[i] = f2[i-1] * 2ll % mod;
// for(int i = 1; i <= 100; i++)
// cout << finv[i] << " === " << qpow(f[i], mod-2) << endl;
qc;
int T;
// cin >> T;
T = 1;
while(T--){
solve();
}
return 0;
}
Tips
- 数组开 l o n g l o n g long \ long long long会爆空间,所以我们要边用边开
- 数据量是 5 e 6 5e6 5e6 所以复杂度可能只支持 O ( n + m ) O(n+m) O(n+m) 很多东西要预处理出来