51nod1197 字符串的数量 V2 dp+矩阵快速幂

123 篇文章 0 订阅
19 篇文章 0 订阅

Description


用N个不同的字符(编号1 - N),组成一个字符串,有如下要求:
(1) 对于编号为i的字符,如果2 * i > n,则该字符可以作为结尾字符。如果不作为结尾字符而是中间的字符,则该字符后面可以接任意字符。
(2) 对于编号为i的字符,如果2 * i <= n,则该字符不可以作为结尾字符。作为中间字符,那么后面接的字符编号一定要 >= 2 * i。
问有多少长度为M且符合条件的字符串,由于数据很大,只需要输出该数Mod 10^9 + 7的结果。
例如:N = 2,M = 3。则abb, bab, bbb是符合条件的字符串,剩下的均为不符合条件的字符串。
n ≤ 1 0 6 , &ThickSpace; m ≤ 1 0 18 n\le 10^6,\; m\le 10^{18} n106,m1018

Solution


考虑这个问题的弱化版本, m ≤ 1 0 6 m\le 10^6 m106的时候要咋做
容易发现2条件只会有log(m)次,并且相邻两次间隔不超过log(m)个,我们可以用这个性质来分段dp
设g[i,j]表示长度恰好为i,最后一位不大于j的一段的方案数。这里一段的定义是只有最后一个*2>n
设s[i]表示长度为i的段数有多少种,f[i]表示长度为i的合法串有多少种,那么有 f [ i ] = ∑ j = 1 i f [ i − j ] ⋅ s [ j ] f[i]=\sum_{j=1}^i{f[i-j]\cdot s[j]} f[i]=j=1if[ij]s[j]
这样做就是 O ( n ( log ⁡ ( m ) + m ) ) O(n(\log(m)+m)) O(n(log(m)+m))

然后可以发现求f的时候只有后60位有用,那么就是抠出来然后矩阵快速幂了

当然你也可以发现这个还可以多项式生成函数来搞,这里就不写了

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))

typedef long long LL;
const int MOD=1000000007;
const int N=1000005;

int f[N],g[2][N],s[N];

struct Mat {
	int r[61][61],n,m;
	Mat() {fill(r,0);}
	int* operator [](int x) {
		return r[x];
	}
	Mat operator *(Mat B) {
		Mat A=*this,C;
		rep(i,1,A.n) rep(j,1,A.m) {
			C[i][j]=0;
			rep(k,1,B.m) C[i][j]=(C[i][j]+1LL*A[i][k]*B[k][j]%MOD)%MOD;
		}
		C.n=A.n,C.m=B.m; return C;
	}
	Mat operator ^(LL dep) {
		Mat X=*this,C=X; dep--;
		for (;dep;dep>>=1) {
			if (dep&1) C=C*X;
			X=X*X;
		}
		return C;
	}
} ;

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void solve(int n,LL m) {
	f[0]=1;
	rep(i,1,std:: min(m,60LL)) rep(j,1,std:: min(i,60)) {
		f[i]=(f[i]+1LL*f[i-j]*s[j]%MOD)%MOD;
	}
}

Mat A,B;

int main(void) {
	int n; LL m; scanf("%d%lld",&n,&m);
	LL wjp=std:: min(m,60LL);
	rep(i,0,n) g[0][i]=1;
	rep(i,1,wjp) {
		fill(g[i&1],0);
		rep(j,1,n) g[i&1][j]=(g[i&1][j-1]+g[(i-1)&1][j/2])%MOD;
		s[i]=(g[i&1][n]-g[i&1][n/2]+MOD)%MOD;
	}
	solve(n,m);
	if (m<=60) return 0&printf("%d\n", f[m]);
	A.n=A.m=60; B.n=1,B.m=60;
	rep(i,1,60) B[1][i]=f[i];
	rep(i,1,59) A[i+1][i]=1;
	rep(i,1,60) A[i][60]=s[61-i];
	A=A^(m-1); B=B*A;
	printf("%d\n", B[1][1]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值