2021牛客多校第二场 B——Cannon

题目大意

像走象棋,棋盘有两行,很多列,第一列上有 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+m4 的值
规则二:
你在下象棋的时候下过第二行之后就不能下第一行了,同样求 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+m4 的值

解题思路

我们首先将 n = n − 2     m = m − 2 n = n-2 \ \ \ m = m-2 n=n2   m=m2
在一行有 n n n 个炮进行一步操作的局面有 2 × ( n − 2 ) 2\times (n-2) 2×(n2) 种,可以这样考虑有 n − 2 n-2 n2 个炮可以向右移动,有 n − 2 n-2 n2个炮可以向左移动。
所以我们在一行有 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=0kCki×2i×Cni×Aii×2ki×Cmki×Akiki
化简一下
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=0k×Cni×Cmki
由组合数的意义,我们化简一下求和式
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=0k2i×Cni×Aii×2ki×Cmki×Akiki
化简一下
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=0k(ni)!n!×(mk+i)!m!
我们上下同时乘以 ( m + n − k ) ! (m+n-k)! (m+nk)!得到
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+mk)!n!×m!i=0kCn+mkni
为了后面计算方便,我们将求和式转化一下
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+mk)!n!×m!i=nknCn+mki
我们发现,求和式是一个组合数的前缀和之差
定义 S ( n , m ) = ∑ i = 0 m C n i S(n, m) = \sum_{i=0}^mC_n^i S(n,m)=i=0mCni
用如下公式来求前缀和
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,m1)=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=nknCn+mki=i=0nCn+mkii=0nk1Cn+mki

我们在计算的时候开一个数组 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+mki
显然 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[i1]=2×s1[i]Cn+min
同时开另外一个数组 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=0nk1Cn+mki
显然 s 2 [ n − 1 ] = 1 s2[n-1] = 1 s2[n1]=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[i1]=2×s2[i]+Cn+mini
然后我们将 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) 很多东西要预处理出来
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值