Codeforces 497E - Subsequences Return(矩阵乘法)

Codeforces 题目传送门 & 洛谷题目传送门

一道还算不错的矩乘 tea 罢,不过做过类似的题应该就比较套路了……

首先考虑对于一个固定的序列 \(\{a\}\) 怎样求其本质不同的序列个数,考虑用一个“动态添加元素”的思想,每次往序列最后添加一个元素 \(x\) 并计算加入这个元素后会新增多少个不同的子序列,显然对于所有原来的子序列,在其后面添上 \(x\) 后得到序列依旧是该序列的子序列,但是我们不能仅仅简简单单地用原来的子序列个数 \(\times 2\) 得到新的序列的子序列个数,因为会出现重复计算的情况。不难发现一个子序列被重复计算当且仅当它的末尾一位是 \(x\),并且它在原来的序列中出现过了。还可以发现所有在原来的序列中出现过,并且末尾一位为 \(x\) 的子序列,去掉末尾一位后仍是原序列的子序列,只有一个例外,那就是单独的 \(x\),因此如果我们记 \(f_{i,x}\) 表示序列 \(\{a_1,a_2,\cdots,a_i\}\) 中有多少个序列以 \(x\) 结尾,那么有 \(f_{i,x}=\sum f_{i-1,y}+1\)。(qwq 其实在这道题我自己的解法中就已经用到这个思想了)

接下来考虑原题,看到数据范围 \(10^{18}\),值域却只有 \(30\),一脸矩乘,并且刚刚 \(dp\) 转移方程式又恰好可以写成矩乘的形式,即假设 \(a_i=x\),那么 \(\begin{bmatrix}f_{i,0}\\f_{i,1}\\f_{i,2}\\\cdots\\f_{i,k-1}\\1\end{bmatrix}=\begin{bmatrix}1&0&0&\cdots&0\\0&1&0&\cdots&0\\\vdots&\vdots&\ddots&\cdots&\vdots\\1&1&1&\cdots&1\\\vdots&\vdots&\vdots&\vdots&\vdots\\0&0&0&\cdots&1\end{bmatrix}\times \begin{bmatrix}f_{i-1,0}\\f_{i-1,1}\\f_{i-1,2}\\\cdots\\f_{i-1,k-1}\\1\end{bmatrix}\),其中转移矩阵满足第 \(x\) 行及对角线上所有元素都是 \(0\),其余元素都是 \(1\)

可是问题又来了,此题序列长度高达 \(10^{18}\),不可能对每个元素都做一遍矩乘,不然复杂度肯定爆炸,有什么优化的办法呢?注意到此题的一个性质,那就是对于所有各位数字和模 \(k\) 相同的整数 \(x,y\) 和非负整数 \(z\),必然有子序列 \(a[x...x+k^z-1]\) 与子序列 \(a[y...y+k^z-1]\) 完全相同,因此我们可以将所有形如 \(a[x...x+k^z]\) 的子序列划分成 \(k\) 个等价类,第 \(i\) 类表示 \(x\) 各位数字和 \(\bmod k=i\) 的那一类,再预处理出 \(A_{x,z}\) 表示 \(a[x...x+k^z]\) 转移矩阵的乘积,那么有 \(A_{x,z}=A_{x,z-1}\times A_{x+1,z-1}\times\cdots\times A_{k-1,z-1}\times A_{0,z-1}\times\cdots\times A_{x-1,z-1}\),暴力计算是 \(k^4\) 的,不过用爪子想想也可以用前后缀积优化到 \(k^3\)

最后求出 \(n\)\(k\) 进制下的表达式,记作 \((a_ma_{m-1}\cdots a_1a_0)_k\),那么所有转移矩阵连乘的结果就是 \(\prod\limits_{i=m}^0\prod\limits_{j=0}^{a_i}A_{j,i}\)(左边的 \(\prod\limits_{i=m}^0\) 表示倒序枚举,写的可能不是特别规范,不过大概意思懂就行了罢),这个随便算算即可,复杂度 \(\log_knk^3\),可以通过此题。

const int LOG=60;
const int MAXM=30;
const int MOD=1e9+7;
ll n;int m,d[LOG+2],dc=-1;
struct mat{
	int a[MAXM+2][MAXM+2];
	mat(){memset(a,0,sizeof(a));}
	mat operator *(const mat &rhs){
		mat ret;
		for(int i=0;i<=m;i++) for(int j=0;j<=m;j++) for(int k=0;k<=m;k++)
			ret.a[i][j]=(ret.a[i][j]+1ll*a[i][k]*rhs.a[k][j])%MOD;
		return ret;
	}
} ans,mul,a[LOG+2][MAXM+2],suf[LOG+2][MAXM+2],pre[LOG+2][MAXM+2];
int main(){
	scanf("%lld%d",&n,&m);
	while(n){d[++dc]=n%m;n/=m;}
	for(int i=0;i<=dc;i++){
		if(!i){
			for(int j=0;j<m;j++){
				a[i][j].a[m][m]=1;
				for(int k=0;k<m;k++) if(k^j) a[i][j].a[k][k]=1;
				for(int k=0;k<=m;k++) a[i][j].a[j][k]=1;
//				printf("A %d %d:\n",i,j);
//				for(int k=0;k<=m;k++) for(int l=0;l<=m;l++)
//					printf("%d%c",a[i][j].a[k][l],(l==m)?'\n':' ');
			}
		} else {
			for(int j=0;j<m;j++){
				if(!j) a[i][j]=suf[i-1][0];
				else a[i][j]=suf[i-1][j]*pre[i-1][j-1];
//				printf("A %d %d:\n",i,j);
//				for(int k=0;k<=m;k++) for(int l=0;l<=m;l++)
//					printf("%d%c",a[i][j].a[k][l],(l==m)?'\n':' ');
			}
		}
		pre[i][0]=a[i][0];
		for(int j=1;j<m;j++) pre[i][j]=pre[i][j-1]*a[i][j];
		suf[i][m-1]=a[i][m-1];
		for(int j=m-2;~j;j--) suf[i][j]=a[i][j]*suf[i][j+1];
	} int sum=0;ans.a[m][0]=1;
	for(int i=0;i<=m;i++) mul.a[i][i]=1;
	for(int i=dc;~i;i--){
		while(d[i]){
			d[i]--;mul=mul*a[i][sum];
			sum++;if(sum==m) sum=0;
		}
	} ans=mul*ans;int ret=0;
	for(int i=0;i<=m;i++) ret=(ret+ans.a[i][0])%MOD;
	printf("%d\n",ret%MOD);
	return 0;
}
posted @ 2021-04-06 09:40  tzc_wk  阅读(65)  评论(0)    收藏  举报