P5371-[SNOI2019]纸牌【矩阵乘法】
正题
题目链接:https://www.luogu.com.cn/problem/P5371
题目大意
有\(n\)种牌,每种牌最多\(C\)张,\(X\)个限制形如\(k_i\)种牌至少\(a_i\)张。
求所有牌的序号能分成\((i,i,i)\)或者\((i,i+1,i+2)\)的若干组的方案数。
\(1\leq n\leq 10^{18},0\leq X,C\leq 1000\)
解题思路
看到这个\(n\)的范围考虑矩阵乘法,然后考虑上面那个叠的东西,因为\((i,i,i)\)能构成一叠,所以三个\((i,i+1,i+2)\)可以分成三个\((i,i,i)\),所以这样的话不难发现一个牌最多有\(6\)张由前或后构成一叠,再进一步的,设\(f_{i,j}\)表示上个选了\(i\)张,这一个选了\(i+j\)张,此时有\(i,j\leq 3\),这样状态数就是\(9\)了,然后暴力矩阵乘法,局部暴力即可。
时间复杂度:\(O(X9^3\log n+XC)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll S=9,P=998244353;
struct Matrix{
ll a[S][S];
}c,ans,f;
ll n,m,C;
Matrix operator*(const Matrix &a,const Matrix &b){
memset(c.a,0,sizeof(c.a));
for(ll i=0;i<S;i++)
for(ll j=0;j<S;j++)
for(ll k=0;k<S;k++)
(c.a[i][j]+=a.a[i][k]*b.a[k][j]%P)%=P;
return c;
}
Matrix Solve(ll k){
memset(c.a,0,sizeof(c.a));
for(ll i=0;i<9;i++){
for(ll j=k;j<=C;j++){
ll x=i/3,y=x+i%3,z=j;
if(z<y)continue;
y-=x;z-=x;
ll s=y*3+(z-y)%3;
c.a[i][s]++;
}
}
return c;
}
void power(Matrix &ans,Matrix &f,ll b){
while(b){
if(b&1)ans=ans*f;
f=f*f;b>>=1;
}
return;
}
signed main()
{
scanf("%lld%lld%lld",&n,&C,&m);
ll z=0;ans.a[0][0]=1;
for(ll i=1,k,w;i<=m;i++){
scanf("%lld%lld",&k,&w);
f=Solve(0);power(ans,f,k-z-1);
ans=ans*Solve(w);z=k;
}
f=Solve(0);power(ans,f,n-z);
printf("%lld\n",ans.a[0][0]);
return 0;
}