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;
}
posted @ 2021-09-28 16:32  QuantAsk  阅读(42)  评论(0编辑  收藏  举报