「SNOI 2019」纸牌

传送门


problem

有一副纸牌。牌一共有 n n n 种,分别标有 1 , 2 , . . . , n 1,2,...,n 1,2,...,n,每种有 C C C 张。故这副牌总共有 n C nC nC 张。

三张连号的牌 ( i , i + 1 , i + 2 ) (i,i+1,i+2) (i,i+1,i+2) 或三张相同的牌 ( i , i , i ) (i,i,i) (i,i,i) 可以组成一叠。如果一组牌可以分成若干(包括零)叠,就称其为一组王牌

你从牌堆中摸了一些初始牌。现在你想挑出一些牌组成一组王牌,请问有多少种可能组成的王牌呢?答案对 998244353 998244353 998244353 取模。

两组牌相同当且仅当它们含有的每一种牌数量都相同。

数据范围: 1 ≤ n ≤ 1 0 18 1\leq n\leq 10^{18} 1n1018 0 ≤ a i ≤ C ≤ 1000 0\leq a_i\leq C\leq 1000 0aiC1000 0 ≤ X ≤ 1000 0\leq X\leq 1000 0X1000。注意 a i a_i ai​ 和 C C C 可能为 0 0 0


solution

为了方便,我们用 [ i ] [i] [i] 来表示 ( i , i + 1 , i + 2 ) (i,i+1,i+2) (i,i+1,i+2) 的牌, { i } \{i\} {i} 表示 ( i , i , i ) (i,i,i) (i,i,i) 的牌。

首先要注意到的一点是,以 i i i 开头的 [ i ] [i] [i] 数量 ≤ 2 \le 2 2。因为一旦出现 3 3 3 [ i ] [i] [i] 就可以看成是 1 1 1 { i } \{i\} {i} 1 1 1 { i + 1 } \{i+1\} {i+1} 1 1 1 { i + 2 } \{i+2\} {i+2} 的组合

根据这一点,我们设 f i , j , k f_{i,j,k} fi,j,k 表示到第 i i i 种牌,有 j j j [ i ] [i] [i],有 k k k [ i − 1 ] [i-1] [i1] 的方案数。

考虑转移。我们枚举 [ i ] [i] [i] 的数量(设有 l l l 个),则有(先不考虑有选择下限的牌):

f i − 1 , j , k × ( ⌊ C − j − k − l 3 ⌋ + 1 ) → f i , l , j f_{i-1,j,k}\times(\lfloor\frac{C-j-k-l}3\rfloor+1)\rightarrow f_{i,l,j} fi1,j,k×(3Cjkl+1)fi,l,j

意思就是,除去 i i i “ “ 顺子 ” ” 中用掉的牌数,剩下的牌凑 { i } \{i\} {i} 然后转移。

其实有下界是一样的,只不过可能要强制选一些 { i } \{i\} {i},特判一下即可。

注意到 n ≤ 1 0 18 n\le10^{18} n1018 O ( n ) O(n) O(n) d p dp dp 是不行的。但是发现转移方程大多是一样的,只有 X X X 个是需要特判的,所以矩阵快速幂优化就可以了。

PS:需要把后两维 ( j , k ) (j,k) (j,k) 压到一个状态,可以用 3 × j + k 3\times j+k 3×j+k 表示。

时间复杂度 O ( 9 3 X log ⁡ n ) O(9^3X\log n) O(93Xlogn)


code

#include
#define N 1005
#define ll long long
#define P 998244353
using namespace std;
ll n,k[N];
int C,X,a[N];
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return (ll)x*y%P;}
struct matrix{
	int m[9][9];
	matrix(int t=0){
		memset(m,0,sizeof(m));
		for(int i=0;i<9;++i)  m[i][i]=t;
	}
	friend matrix operator*(const matrix &A,const matrix &B){
		matrix C(0);
		for(int i=0;i<9;++i)
			for(int k=0;k<9;++k)
				for(int j=0;j<9;++j)
					C.m[i][j]=add(C.m[i][j],mul(A.m[i][k],B.m[k][j]));
		return C;
	}
	friend matrix operator^(matrix A,ll B){
		matrix ans(1);
		for(;B;B>>=1,A=A*A)  if(B&1)  ans=ans*A;
		return ans;
	}
}A,B,tmp;
void init(){
	for(int i=0;i<=2;++i)
		for(int j=0;j<=2;++j)
			for(int k=0;k<=2;++k)
				if(i+j+k<=C)  B.m[j*3+k][i*3+j]=(C-i-j-k)/3+1;
}
int main(){
	scanf("%lld%d%d",&n,&C,&X),init();
	for(int i=1;i<=X;++i)  scanf("%lld%d",&k[i],&a[i]);
	A.m[0][0]=1;
	for(int i=1;i<=X;++i){
		A=A*(B^(k[i]-1-k[i-1]));
		memset(tmp.m,0,sizeof(tmp.m));
		for(int j=0;j<=2;++j){
			for(int k=0;k<=2;++k){
				for(int l=0;l<=2;++l){
					int now=j+k+l,must=(now>a[i])?now:(a[i]+((now-a[i])%3+3)%3);
					if(must<=C)  tmp.m[j*3+k][l*3+j]=(C-must)/3+1;
				}
			}
		}
		A=A*tmp;
	}
	A=A*(B^(n-k[X]));
	printf("%d\n",A.m[0][0]);
	return 0;
}

你可能感兴趣的:(#,快速幂)