bzoj2323: [ZJOI2011]细胞
这题真神。。。
首先看到这么花里胡哨的题面眉头一皱就发现这个球的大小是搞笑的不然就没法做了,有用的是最终拆出来的长度
然后对于一段长度为n有n-1个丝状物的东西,写一个DP:f[i][2]表示枚举到第i个丝状物,当前断不断
那f[i][0]=f[i-1][1] f[i][1]=f[i-1][0]+f[i-1][1]=f[i-2][1]+f[i-1][1] 最终答案是f[n-1][1],把[1]去掉就是一个斐波那契数列
设c[n]表示长度为n一共构成的方案数,c[n]=fib[n-1]
那么题意转化为给你一个序列,分成若干段,求∑fib[(∑di)-1] di是每一段表示的数字
fib肯定是要用矩阵乘法求的了,那么相当于求∑A * (M^(∑di)-2)) =A * (∑M^(∑di)) * (M^-2) 注意这里(M^-2)不能放在前面,矩乘没有交换律
要算的就是∑M^(∑di)=∑∑M^di(分配律),我们发现di实在是太大了,假如一次都不分割可以到1000位
我们把d的每一个十进制位拆出来,∑∑M^di=∑∑M^(∑ai*mj) 其中ai表示d的从左到右第i位的数字,mj是10^j,j表示i在d中是从右往左的第j+1位
再开出来∑∑M^(∑ai*mj)=∑∑∑(M^mj)^ai,M^mj可以预处理,设为gj的话原式=∑∑∑gj^ai
整理一下,现在的做法是枚举每一个状态,枚举这个状态的每一个段,枚举这个状态的每一个位把它的贡献加上
但是状态实在是太多了!我们必须再优化
设fi表示前i个细胞已经分好了上面那坨东西的和,因为有分配律所以我们可以先把不同状态当前的和先加起来
对于转移 fi=∑fj* c(j+1,i) 其中c(j+1,i)表示j+1~i变成一个段对答案的贡献,其实就是∑gj^ai嘛
直接这样做是n^3的,但是反过来for就可以把后面那个∑消掉
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; const int maxn=1100; const LL mod=1e9+7; struct Matrix { LL mp[3][3]; void clear(){memset(mp,0,sizeof(mp));} void init(){clear();mp[1][1]=1;mp[2][2]=1;} friend Matrix operator +(Matrix a,Matrix b) { Matrix c;c.clear(); for(int i=1;i<=2;i++) for(int j=1;j<=2;j++) c.mp[i][j]=(a.mp[i][j]+b.mp[i][j])%mod; return c; } friend Matrix operator *(Matrix a,Matrix b) { Matrix c;c.clear(); for(int i=1;i<=2;i++) for(int j=1;j<=2;j++) for(int k=1;k<=2;k++) c.mp[i][j]=(c.mp[i][j]+a.mp[i][k]*b.mp[k][j])%mod; return c; } friend Matrix operator ^(Matrix a,int p) { Matrix c;c.init(); while(p!=0) { if(p%2==1)c=c*a; a=a*a;p/=2; } return c; } }mi[maxn];//fib矩阵的10^k char ss[maxn]; int a[maxn];Matrix f[maxn],s; int main() { mi[0].mp[1][1]=0,mi[0].mp[1][2]=1; mi[0].mp[2][1]=1,mi[0].mp[2][2]=1; for(int i=1;i<=1000;i++)mi[i]=mi[i-1]^10; mi[1010].mp[1][1]=mod-1,mi[1010].mp[1][2]=1; mi[1010].mp[2][1]=1,mi[1010].mp[2][2]=0;//fib逆运算 mi[1011].mp[1][1]=0,mi[1011].mp[1][2]=1;//A //.......init............ int n; scanf("%d",&n); scanf("%s",ss+1); for(int i=1;i<=n;i++)a[i]=ss[i]-'0'; f[0].init(); for(int i=1;i<=n;i++) { s.init(); for(int j=i-1;j>=0;j--) { s=s*(mi[i-j-1]^a[j+1]); f[i]=f[i]+f[j]*s; } } f[n]=mi[1011]*f[n]*mi[1010]*mi[1010]; printf("%lld\n",f[n].mp[1][2]); return 0; }