题解 P4541 【[ZJOI2011]细胞】
shadowice1984 · · 题解
首先先来翻译一下并不明确的题意
给你一个长度为1000的仅含1~9的字符串,现在我们将它随意的切成一些段并且每段的长度至少为1,将切开的每一段数视为一个十进制数字,并且将他们求和,记这个和为T
那么我希望你求出
也就是说我们枚举所有可能的划分然后计算出一个
好了那这个题意看起来可能有点过于和题面无关了
那么我们来解释一下这个式子是怎么推出来的
首先我们根据题意可以得到在第一步分裂之后方案数的多少仅仅和小球的数目有关,而小球的数目其实就是分割出的数字之和,也就是
那么我们直接考虑
那么首先我们的第一条边肯定是要退化的这个显然没有任何问题(否则这个结构就不合法了因为第一个球没有退化边)
但是第二条边退不退化就说不好了
那么假如第2个边退化,那么我们就递归到了一个第2个边被钦定退化的子问题,这个子问题的方案数就是
否则第3条边就必须退化了,那么我们就递归到了一个第3条边被钦定退化的子问题,这个子问题的方案数就是
所以我们可以推出一个有趣的递归式
应该都知道
那么由于
那么问题来了,我们该怎么求呢?
这里如果是一个一般一些的数列我们可以使用矩阵乘法
不过这题的话我们会发现我们要求的东西似乎是斐波那契数列
所以众所周知,斐波那契数列有一个通项公式
等等这公式里全是无理数你让我怎么算?
注意到我们是在模剩余系下做到运算,如果我们可以找到一个数
换句话说我们就是要找到5的二次剩余
然后我们暴力的试了一下发现在模
怎么办呢?
如果在实数里面我们开方失败的时候会强行定义一个
因此我们在膜剩余系下也可以使用同样的套路
我们定义数对
那么我们会发现
换句话说我们发现这样的数对
好了我们接着来看如何计算题目中给定的式子
因此我们分别计算两个次幂的和就可以了
那这个东西怎么计算呢?
我们大力
设
其中
那么
很简单我们考虑这样一个过程假如我们已经知道了底数的
那么我们可以得到一个这样的等式就是
所以我们预处理一下
然后我们就可以愉快的计算啦~
对了这个算法常数会比矩乘的做法小很多很多,目前这份代码暂时是rk1.比rk2快3倍
上代码~
// luogu-judger-enable-o2
#include<cstdio>
#include<algorithm>
using namespace std;const int N=1e3+10;typedef long long ll;const ll mod=1e9+7;
inline ll po(ll a,ll p){ll r=1;for(;p;p>>=1,a=a*a%mod)if(p&1)r=r*a%mod;return r;}
int n;int m;char mde[N];
struct cmp//手写复数类
{
ll r;ll v;
friend cmp operator +(cmp a,cmp b){return (cmp){(a.r+b.r)%mod,(a.v+b.v)%mod};}
friend cmp operator *(cmp a,cmp b){return (cmp){(a.r*b.r+5*a.v*b.v)%mod,(a.r*b.v+a.v*b.r)%mod};}
friend cmp operator -(cmp a,cmp b){return (cmp){(a.r+mod-b.r)%mod,(a.v+mod-b.v)%mod};}
friend cmp operator /(cmp a,cmp b)
{
ll inv=po((b.r*b.r+mod-5*b.v*b.v%mod)%mod,mod-2);
return (cmp){(a.r*b.r%mod+mod-5*a.v*b.v%mod)*inv%mod,(a.v*b.r%mod+mod-a.r*b.v%mod)*inv%mod};
}
}dp[N],mi[N],tmi[N],cst[N];
inline cmp po(cmp a,int p){cmp r=(cmp){1,0};for(;p;p>>=1,a=a*a)if(p&1)r=r*a;return r;}
inline cmp solve(cmp bas)//计算其中的一个幂次
{
tmi[0]=mi[0]=(cmp){1,1};tmi[1]=mi[1]=bas;
for(int i=2;i<=9;i++)tmi[i]=tmi[i-1]*tmi[1];
for(int i=0;i<=n;i++)cst[i]=(cmp){1,0};dp[0]=(cmp){1,0};
for(int i=1;i<=n;i++)
{
dp[i]=(cmp){0,0};
for(int j=0;j<i;j++)cst[j]=po(cst[j],10)*tmi[mde[i]-'0'];
for(int j=0;j<i;j++)dp[i]=dp[i]+dp[j]*cst[j];
}dp[n]=dp[n]/bas;dp[n]=dp[n]*(cmp){0,400000003};return dp[n];
}
int main()
{
scanf("%d",&n);scanf("%s",mde+1);//然后直接暴力套通项公式就行了
printf("%lld",(solve((cmp){500000004,500000004})-solve((cmp){500000004,500000003})).r);
return 0;
}