给定 m,记 f(n) 表示用 1×2 的骨牌覆盖 m×n 的网格的方案数。给定 l,r,k,你需要求:
r−l+11i=l∑r(kf(i))
答案对 p=998,244,353 取模。
1≤l≤r≤6×1018,p∤r−l+1,m∈{2,3},1≤k≤501。
由于 m∈{2,3},所以可以对于 m 的不同取值分类讨论,求出此时 f(n) 的表达式。
对于 m=2,我们发现骨牌覆盖一定由下面两种基本型组成:

所以很容易列出递推式:
f(i)={f(i−1)+f(i−2)1i≥2i≤1
那么就可以注意到:
f(n)=fib(n+1)=55−(21−5)n+1+(21+5)n+1
然后是 m=3,首先由三种平凡的基本型,它们都是 3×2 的:

然后其实还有两种方法,类似这样,它们是 3×2n 的(其中 n∈[2,∞)∩N):

即第一行 / 最后一行全是 1×2 的骨牌覆盖,两侧是 2×1 的骨牌覆盖,其余部分都是 1×2 的骨牌覆盖。
那么可以列出递推式:
g(n)=3g(n−2)+2k=2∑⌊2n⌋g(n−2k)
边界 g(0)=1,g(1)=0。
注意到 g(n) 只会被 g(m)(其中 n≡m(mod2) 且 m<n)贡献,又因为 g(1)=0,所以我们发现 g(n)=0 当且仅当 2∤n。
不妨令 g′(n)=g(2n),则可以得到:
g′(n)=3g′(n−1)+2k=2∑ng′(n−k)=3g′(n−1)+2k=0∑n−2g′(k)
边界 g′(0)=1,现在需要找到它的通项公式,不妨构造它的生成函数:
G′(z)(1−z)(1−3z)G′(z)((1−z)(1−3z)−2z2)G′(z)G′(z)=3zG′(z)+21−zz2G′(z)+1=2z2G′(z)+1−z=1−z=(1−z)(1−3z)−2z21−z=1−4z+z21−z
这也是一个有理式,因此它是一个(二阶)线性递推,尝试推导它的通项公式:
G′(z)=1−4z+z21−z=(z−2+3)(z−2−3)1−z=z−2+3A+z−2−3B
其中:
A(z−2−3)+B(z−2+3)=(A+B)z+(−2−3)A+(−2+3)=1−z⟹{A+B=−1(−2−3)A+(−2+3)B=1⟹{A=−63−3B=−63+3
由于:
z−2+3A=Ai≥0∑(−1)izi(3−2)−1−i=i≥0∑A(−1)i(−2−3)i+1zi=i≥0∑−A(2+3)i+1ziz−2−3B=Bi≥0∑(−1)izi(−3−2)−1−i=i≥0∑B(−1)i(3−2)i+1zi=i≥0∑−B(2−3)i+1zi
故:
g′(n)=[zn]G′(z)=63−3(2+3)n+1+63+3(2−3)n+1
因此:
g(n)={63−3(2+3)2n+1+63+3(2−3)2n+102∣n2∤n
根据通项公式的形式,可以发现最终我们求解的问题可以转换为:
给定 n,k,α,β,x,y,你需要求:
i=0∑n(kαxi+βyi)
尝试用下降幂转普通幂公式推式子:
i=0∑n(kαxi+βyi)=k!1i=0∑n(αxi+βyi)k=k!1i=0∑nj≥0∑(−1)k−j[jk](axi+βyi)j=k!1j≥0∑(−1)k−j[jk]i=0∑n(αxi+βyi)j
考察内侧的和式(这一部分的 k 被用来当求和指标了,另外我们认为 xkyj−k=1):
i=0∑n(αxi+βyi)j=i=0∑nk≥0∑(kj)(αxi)k(βyi)j−k=k≥0∑(kj)i=0∑n(αxi)k(βyi)j−k=k≥0∑(kj)αkβj−ki=0∑nxikyij−ik=k≥0∑(kj)αkβj−ki=0∑n(xkyj−k)i=k≥0∑(kj)αkβj−k⋅xkyj−k−1(xkyj−k)n+1−1
计算后面这个东西,时间复杂度 O(jlogn),所以总时间复杂度 O(k2logn)。
你以为这就完了吗?实际上是没有的。因为 3,5 在 Z/pZ 种都是非二次剩余的!换句话说我们在 Z/pZ 中找不到和式的 α,β,x,y!
我们可以在 Mathematica 检验这一点:
{JacobiSymbol[5, 998244353], JacobiSymbol[3, 998244353]} (* 输出 {-1, -1} *)
不过这也是很容易解决的,下面以 3 为例。
我们定义 j=3,那么 j 与 1 在有限域 Z/pZ 上线性无关,所以可以构造一组次数为 2 的域扩张 K,选取基底 {1,j}(即 ∀i∈K,∃a,b∈Z/pZ,i=a+bj。那么 K 亦是域,因此可以进行所有原本 Z/pZ 上的运算。这样就解决了不存在 3 的问题。
时间复杂度 O(k2logn)。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 505;
constexpr int mod = 998244353, inv2 = (mod + 1) >> 1;
int Add(int x, int y){ return (x + y) >= mod ? (x + y - mod) : (x + y); }
int Sub(int x, int y){ return (x - y) < 0 ? (x - y + mod) : (x - y); }
int Mul(int x, int y){ return 1ll * x * y % mod; }
int fastpow(int x, int y){
int ret = 1;
for(;y;y>>=1,x=Mul(x, x)){ if(y & 1) ret = Mul(ret, x); }
return ret;
}
template<int S>
struct Quadratic{
int a, b;
using T = Quadratic<S>;
Quadratic(int a = 0, int b = 0): a(a), b(b) {}
T operator+(const T& rhs) const { return {Add(a, rhs.a), Add(b, rhs.b)}; }
T operator-(const T& rhs) const { return {Sub(a, rhs.a), Sub(b, rhs.b)}; }
T operator*(const T& rhs) const {
return {Add(Mul(a, rhs.a), Mul(Mul(b, rhs.b), S)), Add(Mul(a, rhs.b), Mul(b, rhs.a))};
}
T operator~() const {
int inv = fastpow(Sub(Mul(a, a), Mul(Mul(b, b), S)), mod - 2);
return {Mul(a, inv), Mul(mod - b, inv)};
}
T operator/(const T& rhs) const { return *this * ~rhs; }
template<class U>
T operator^(U y) const {
T a = *this, ret = 1;
for(;y;y>>=1,a=a*a){
if(y & 1) ret = ret * a;
}
return ret;
}
int extract() const { return assert(!b), a; }
T conj() const { return {a, mod - b}; }
};
template<int S>
using Q = Quadratic<S>;
using Q5 = Q<5>;
using Q3 = Q<3>;
int binom[N][N], stirling[N][N], m;
void init(int n){
for(int i=0;i<=n;i++){
binom[i][0] = 1;
stirling[i][0] = i == 0;
for(int j=1;j<=i;j++){
binom[i][j] = Add(binom[i - 1][j], binom[i - 1][j - 1]);
stirling[i][j] = Add(Mul(i - 1, stirling[i - 1][j]), stirling[i - 1][j - 1]);
}
}
}
template<int S>
Q<S> solve(i64 n, int k, Q<S> a, Q<S> b, Q<S> x, Q<S> y){
Q<S> ans;
for(int j=0;j<=k;j++){
Q<S> ret = 0;
for(int i=0;i<=j;i++){
Q<S> t = (x ^ i) * (y ^ (j - i)), tmp;
if(t.a == 1 && t.b == 0) tmp = (n + 1) % mod;
else tmp = ((t ^ (n + 1)) - 1) / (t - 1);
ret = ret + tmp * binom[j][i] * (a ^ i) * (b ^ (j - i));
}
if((k - j) & 1) ans = ans - ret * stirling[k][j];
else ans = ans + ret * stirling[k][j];
}
for(int i=1;i<=k;i++) ans = ans * fastpow(i, mod - 2);
return ans;
}
int solve(i64 n, int k){
if(m == 2){
Q5 a = Q5(0, mod - 1) / 5, b = a.conj();
Q5 x = Q5(inv2, mod - inv2), y = x.conj();
a = a * x, b = b * y;
return solve(n, k, a, b, x, y).extract();
}
else{
Q3 a = Q3(inv2, mod - fastpow(6, mod - 2)), b = a.conj();
Q3 x = Q3(2, 1), y = x.conj();
a = a * x, b = b * y;
return solve(n, k, a, b, x, y).extract();
}
}
void solve(){
i64 L, R; int k;
cin >> L >> R >> k;
int ans = fastpow((R - L + 1) % mod, mod - 2);
if(m == 3) L = (L + 1) >> 1, R >>= 1;
cout << Mul(ans, Sub(solve(R, k), solve(L - 1, k))) << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t; cin >> t >> m;
init(504);
while(t--) solve();
return 0;
}
// Written by xiezheyuan