题解 P6435 【「EZEC-1」数列】

传送门

久违地来一波题解,来一个数学角度推式子的方法


【分析】

记第 \(k\) 次合并后的第 \(i\) 个数为 \(F_{k,i}\)

根据题意,所求即为 \(ans=F_{n-1,n}\) ,且有:

\(F_{k+1,n}=aF_{k,n}+bF_{k,n+1}+c,F_{0,i}=i\)

形式上,我们设 \(\exists d\wedge F_{k+1,i}+d=a(F_{k,i}+d)+b(F_{k,i+1}+d)\)

则不难换算出 \((a+b-1)d=c\)

注意,只是形式上这样设, \(d\) 可能本身不存在

\(G_{k,i}=F_{k,i}+d\) 则得到: \(G_{k+1,i}=aG_{k,i}+bG_{k,i+1}\)

考虑 \(G\) 之间的转移,发现与杨辉三角类似,故考虑每个 \(G_{0,i}\) 的贡献,很容易得到:

\(\displaystyle G_{n-1,n}=\sum_{i=1}^n\dbinom{n-1}{i-1}a^{(n-1)-(i-1)}b^{i-1}G_{0,i}\)

代回 \(G_{k,i}=F_{k,i}+d,F_{0,i}=i,ans=F_{n-1,n}\) ,换元 \(i=i-1\)

\(\displaystyle ans=\sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}b^i(i+1+d)-d\)

\(d\) 展开后得到:

\(\displaystyle ans=\sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}b^i\cdot (i+1)+d[\sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}b^i-1]\)

后半部分是个很显然的二项式定理,求和得到 \((a+b)^{n-1}\)

再代回 \((a+b-1)d=c\) 得到:\(\displaystyle ans=\sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}b^i(i+1)+c\cdot {(a+b)^{n-1}-1\over (a+b)-1}\)

接着,我们构造函数,设 \(\displaystyle f(x)=\sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}x^{i+1}=x(a+x)^{n-1}\)

因此 \(\displaystyle {\text d\over \text dx}f(x)=\sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}x^i\cdot (i+1)={\text d\over \text dx}[x(a+x)^{n-1}]=(a+x)^{n-1}+x\cdot (a+x)^{n-2}\cdot (n-1)\)

整理得到: \(\displaystyle \sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}x^i\cdot (i+1)=(a+x)^{n-2}\cdot (a+nx)\)

所以得到 \(\displaystyle \sum_{i=0}^{n-1}\dbinom{n-1}ia^{(n-1)-i}b^i\cdot (i+1)=(a+b)^{n-2}\cdot (a+nb)\)

最后我们得到: \(ans=(a+b)^{n-2}\cdot (a+nb)+c\cdot {1-(a+b)^{n-1}\over 1-(a+b)}\)


法一:

对于前半部分,快速幂 \(O(\log n)\) 可直接算出;而后半部分,可能不存在 \(1-(a+b)\) 的逆元,不能直接求解

考虑到其形式上为等比数列求和式,展开为: \(1+(a+b)+(a+b)^2+\cdots +(a+b)^{n-2}\)

\(sumq(q,n)=1+q+q^2+\cdots +q^n\)

则取一个数 \(m\) ,使得 \(1+q+q^2+\cdots +q^n=(1+q+q^2+\cdots +q^m)(1+q^{m+1}+q^{2(m+1)+\cdots}+q^{\lfloor{n-m\over m+1}\rfloor\cdot (m+1)})+q^{m+\lfloor{n-m\over m+1}\rfloor\cdot (m+1)+1}(1+q+\cdots)\)

因此 \(sumq(q,n)=sumq(q,m)\cdot sumq(q^{m+1},\lfloor{n-m\over m+1}\rfloor)+q^{m+\lfloor{n-m\over m+1}\rfloor\cdot (m+1)+1}\cdot sumq(q,n-m-\lfloor{n-m\over m+1}\rfloor\cdot (m+1)-1)\) ,可以递归求解

通过估算,大概 \(m=\sqrt n\) 时最优


现分析求 \(sumq(q,n)\) 的复杂度:

\(T(n)\approx T(m)+T(m)+T(n-m^2)+\log m+\log n\leq 3T(n^{1\over 2})+{3\over 2}\log n\)

\(k=\log n\)\(T(k)=3T({k\over 2})+{3\over 2}k\)

再记 \(2^a=k\)\(T(a)=3T(a-1)+{3\over 2}\cdot 2^a\)

解递推方程得到 \(T(a)=3^a+o(3^a)=O(3^a)\)

代回得 \(3^a=2^{a\log_2 3}=k^{\log_2 3}=\log^{\log_2 3} n\)

因此 \(T(n)=O(\log^{\log_2 3} n)\approx O(\log^{1.6} n)\)


【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,a,b,c,p,ans=1;
inline __int128 fpow(__int128 a,ll x) { ll ans=1; for(;x;x>>=1,a=a*a%p) if(x&1) ans=ans*a%p; return ans; }
inline __int128 sumq(__int128 q,ll n){
    if(n==0) return 1%p;
    if(n==1) return (q+1)%p;
    ll m=sqrt(n),tmp=(n-m)/(m+1)*(m+1)+m;
    __int128 res=sumq(q,m)*sumq( fpow(q,m+1) , (n-m)/(m+1) )%p;
    if(tmp<n) res+=fpow(q,tmp+1)*sumq(q,n-tmp-1)%p;
    return (res%p+p)%p;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin>>n>>a>>b>>c>>p;
    if(n>1) ans=fpow(a+b,n-2)*(a+(__int128)n*b%p)%p+c*sumq(a+b,n-2)%p;
    ans=(ans%p+p)%p;
    cout<<ans;
    cout.flush();
    return 0;
}

法二:

想了想,好像没那么复杂

考虑到 \(a,b\geq 1\),所以 \(a+b>1\) 一定成立

因此,前半部分同样快速幂直接跑

后半部分为形如 \({q^n-1\over q-1},q>1\) 的形式

\({q^n-1\over q-1}\bmod p=t\)

\({q^n-1\over q-1}-kp=t\)

移项得到 \(q^n-kp(q-1)=t(q-1)+1\)

因此 \(q^n\bmod p(q-1)=t(q-1)+1\)

由于我们所求为 \(t\) ,因此得出:

\(t={q^n\bmod p(q-1)-1\over q-1}\)

我们直接把 \(q\) 在模 \(p(q-1)\) 的意义下跑快速幂,结果直接进行这个处理,就能得到我们索要的结果了

其中,\(p(q-1)\leq 10^{14}\cdot (10^9+10^9)=2\times 10^{23}\) ,要使用 __int128

由于数值过大,甚至对 __int128 写拆位的快速乘都不行,因此只能龟速乘

时间复杂度为 \(O(\log^2 n)\) ,但由于没有递归,运行速率好像反而比法一快


【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,a,b,c,p,ans=1;
inline __int128 fpow(__int128 a,__int128 x,__int128 m) { __int128 ans=1; for(;x;x>>=1,a=a*a%m) if(x&1) ans=ans*a%m; return ans; }
//不用龟速乘的快速幂
inline __int128 fmul(__int128 a,__int128 x,__int128 m) { __int128 ans=0; for(;x;x>>=1,a=(a+a)%m) if(x&1) ans=(a+ans)%m; return ans; }
inline __int128 fpoww(__int128 a,__int128 x,__int128 m) { __int128 ans=1; for(;x;x>>=1,a=fmul(a,a,m)) if(x&1) ans=fmul(a,ans,m); return ans; }
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin>>n>>a>>b>>c>>p;
    if(n>1) ans=fpow(a+b,n-2,p)*(a+(__int128)n*b%p)%p+(fpoww(a+b,n-1,(__int128)p*(a+b-1))-1)/(a+b-1)*c%p;
    ans=(ans%p+p)%p;
    cout<<ans;
    cout.flush();
    return 0;
}

虽然必须用龟速乘,但我们可以对龟速乘搞事情:

由于 \(127-\log_2 (2\times 10^{23})\approx49\)

所以我们龟速乘可以48位、48位地算,这样就只要3次计算

(稍微卡了一下常数)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,a,b,c,p,ans=1;
inline ll fpow(__int128 a,ll x,ll m) { ll ans=1; for(;x;x>>=1,a=a*a%m) if(x&1) ans=ans*a%m; return ans; }
//不用龟速乘的快速幂
inline __int128 fmul(__int128 a,__int128 x,__int128 m) { __int128 ans=0; for(;x;x>>=48,a=(a<<48)%m) ans=(a*(x&(1ll<<48)-1)+ans)%m; return ans; }
inline __int128 fpoww(__int128 a,__int128 x,__int128 m) { __int128 ans=1; for(;x;x>>=1,a=fmul(a,a,m)) if(x&1) ans=fmul(a,ans,m); return ans; }
int main(){
    cin>>n>>a>>b>>c>>p;
    if(n>1) ans=fpow(a+b,n-2,p)*(a+(__int128)n*b%p)%p + (fpoww(a+b,n-1,(__int128)p*(a+b-1))-1)/(a+b-1)*c%p +p;
    cout<<ans%p;
    return 0;
}

目前好像是榜一

开了 o2

不开 o2

posted @ 2021-01-30 11:36  JustinRochester  阅读(152)  评论(0编辑  收藏  举报