P5059 中国象棋
首先中国象棋是放在格点上的,所以先 \(n \gets n+1\) 转化为放在方格上。样例因为这个看了好久没看懂
又由于每个卒只能攻击与其相邻的两个卒,所以容易发现行与行之间是独立的,所以只考虑一行中的情况。
对于每行至少放两个棋子的限制,可以先抛开不管,最后减去只放一个或不放的 \(n+1\) 种情况。
设 \(dp_{i,1/0}\) 表示在当前行中考虑到第 \(i\) 位,第 \(i\) 位放或不放的方案数,由于不能有相邻的两个卒,于是可以得到如下转移:
\[\begin{aligned}
&dp_{i,1}=dp_{i-1,0}\\
&dp_{i,0}=dp_{i-1,1}+dp_{i-1,0}\\
\end{aligned}
\]
因为 \(N \leq 10^{18}\),所以可以用矩阵加速 \(\text{dp}\)。
\[\begin{vmatrix}dp_{i-1,0}&dp_{i-1,1}\end{vmatrix}\begin{vmatrix}1&1\\1&0\end{vmatrix}=\begin{vmatrix}dp_{i,0}&dp_{i,1}\end{vmatrix}
\]
求出每一行的方案以后,根据乘法原理,总方案数为每行的方案数之积。
计算过程可能爆 \(\text{long long}\),要开 \(\text{int128}\)。
时间复杂度 \(O(8 \log n)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll SIZE = 200005;
ll n, mod;
inline ll rd(){
ll f = 1, x = 0;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return f*x;
}
inline void wt(ll x) {
if(x<0) {
putchar('-');
x=-x;
}
if(x>9)
wt(x/10);
putchar(x%10+'0');
}
struct node{
ll o[2][2];
node(){
for(ll i = 0; i < 2; i++)
for(ll j = 0; j < 2; j++)
o[i][j] = 0;
}
};
node mul(node A, node B){
node jl;
for(ll i = 0; i < 2; i++)
for(ll j = 0; j < 2; j++)
for(ll k = 0; k < 2; k++)
jl.o[i][j] = (jl.o[i][j] + ((__int128)A.o[i][k] * B.o[k][j] % mod)) % mod;
return jl;
}
node power(node x, ll y){
node jl;
for(ll i = 0; i < 2; i++) jl.o[i][i] = 1;
while(y){
if(y&1) jl = mul(jl, x);
x = mul(x, x);
y >>= 1;
}
return jl;
}
ll power(ll x, ll y){
ll jl = 1;
while(y){
if(y&1) jl = ((__int128)jl * x) % mod;
x = ((__int128)x * x) % mod;
y >>= 1;
}
return jl;
}
int main(){
n = rd()+1, mod = rd();
if(n <= 2){
printf("0");
return 0;
}
node ans;
ans.o[0][0] = ans.o[0][1] = ans.o[1][0] = 1; ans.o[1][1] = 0;
ans = power(ans, n-1);
ll jl = ((ans.o[0][0] + ans.o[0][1])%mod + (ans.o[1][0] + ans.o[1][1])%mod)%mod;
jl = ((jl - n - 1) % mod + mod) % mod;
wt(power(jl, n));
return 0;
}