【CF497E】Subsequences Return 矩陣乘法

【CF497E】Subsequences Return

題意:設$s_k(x)$表示x在k進制下各位數的和mod k的值。給出k,現有序列$s_k(1),s_k(2),...s_k(n)$。求這個序列有多少個本質不一樣的子序列。ios

$n\le 10^{18},k\le 30$優化

題解:狀態很是巧妙(其實作過相似套路就知道了)。看到$n=10^{18}$就必定是讓你矩乘了。咱們但願構建出一個相似於自動機的東西,它能識別出一個序列的全部子序列,且點數最好是在$O(k)$級別的,怎麼辦呢?spa

假如咱們真的構建出了一個自動機,那麼對於他的一個狀態x,如今新來了一個數a,若是a是x想要的,那麼從x轉移到其它狀態,不然轉移到本身。那咱們不妨直接設x這個狀態表示它下一個想要的數是x的方案數。若是匹配成功,則下一個想要的數能夠是任意數,並使計數器+1,不然它想要的數仍是本身。blog

接着考慮怎麼矩乘,容易想到將x放到k進制下表示。用$A_{i,j}$表示$s_k(j\times k^i)..s_k((j+1)\times k^i-1)$這段數對應的轉移矩陣。那麼$A_{i,j}$其實就是$A_{i-1,j}A_{i-1,j+1}...A_{i-1,k-1}A_{i-1,0}...A_{i-1,j-1}$。用前綴和優化一下便可。string

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll P=1000000007;
int m,len;
ll n;
ll v[61];

struct M
{
	ll v[31][31];
	M () {memset(v,0,sizeof(v));}
	ll * operator [] (const int &a) {return v[a];}
	M operator * (const M &a) const
	{
		M b;
		int i,j,k;
		for(i=0;i<=m;i++)	for(j=0;j<=m;j++)	for(k=0;k<=m;k++)	b.v[i][j]=(b.v[i][j]+v[i][k]*a.v[k][j])%P;
		return b;
	}
}T[60][30],S,s1[60][30],s2[60][30];

int main()
{
	scanf("%lld%d",&n,&m);
	v[0]=n;
	while(v[len])	v[len+1]=v[len]/m,v[len]%=m,len++;
	int i,j,a,b;
	for(i=0;i<=m;i++)	S[0][i]=1;
	for(i=0;i<len;i++)
	{
		for(j=0;j<=m;j++)	T[i][0][j][j]=1;
		if(!i)
		{
			for(j=0;j<m;j++)
			{
				T[i][j][m][m]=1;
				for(a=0;a<m;a++)
				{
					if(a!=j)
					{
						T[i][j][a][a]=1;
						continue;
					}
					for(b=0;b<=m;b++)	T[i][j][a][b]=1;
				}
			}
		}
		else
		{
			for(j=0;j<m;j++)
			{
				if(!j)	T[i][j]=s2[i-1][0];
				else	T[i][j]=s2[i-1][j]*s1[i-1][j-1];
			}
		}
		for(s1[i][0]=T[i][0],j=1;j<m;j++)	s1[i][j]=s1[i][j-1]*T[i][j];
		for(s2[i][m-1]=T[i][m-1],j=m-2;j>=0;j--)	s2[i][j]=T[i][j]*s2[i][j+1];
	}
	for(i=len-1,j=0;i>=0;i--)
	{
		while(v[i]--)	S=S*T[i][j],j=(j+1)%m;
	}
	printf("%lld",S[0][m]);
	return 0;
}//1000000000000000000 2
相關文章
相關標籤/搜索