zoukankan      html  css  js  c++  java
  • P5371-[SNOI2019]纸牌【矩阵乘法】

    正题

    题目链接:https://www.luogu.com.cn/problem/P5371


    题目大意

    (n)种牌,每种牌最多(C)张,(X)个限制形如(k_i)种牌至少(a_i)张。

    求所有牌的序号能分成((i,i,i))或者((i,i+1,i+2))的若干组的方案数。

    (1leq nleq 10^{18},0leq X,Cleq 1000)


    解题思路

    看到这个(n)的范围考虑矩阵乘法,然后考虑上面那个叠的东西,因为((i,i,i))能构成一叠,所以三个((i,i+1,i+2))可以分成三个((i,i,i)),所以这样的话不难发现一个牌最多有(6)张由前或后构成一叠,再进一步的,设(f_{i,j})表示上个选了(i)张,这一个选了(i+j)张,此时有(i,jleq 3),这样状态数就是(9)了,然后暴力矩阵乘法,局部暴力即可。
    时间复杂度:(O(X9^3log 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
    ",ans.a[0][0]);
    	return 0;
    }
    
  • 相关阅读:
    线段的类
    计算三角形的类
    关于狗的类
    [poj2234] Matches Game
    bzoj[2655] calc
    拉格朗日插值和牛顿插值 菜鸟教程
    NOI模拟赛(3.15) sequence(序列)
    NOI模拟赛(3.13)Hike (远行)
    二分图讲解
    NOI模拟赛(3.8)Problem B
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/15348598.html
Copyright © 2011-2022 走看看