YbtOJ#732-斐波那契【特征方程,LCT】

1|0正题

题目链接:http://www.ybtoj.com.cn/contest/125/problem/2


1|1题目大意

给出n个点的一棵树,以1为根,每个点有点权ai。要求支持m次操作

  1. 修改一个修改一个节点的父节点
  2. 修改一条路径的权值为w
  3. 给出u询问Fbi(au)
  4. 给出u,v,将路径u>v的点权排列好后设为b

i=1kj=ikFbi(z=ijbz)

其中Fbi(i)表示第i个斐波那契数。输出答案模998244353的值

1n,m105,ai,w[1,109]


1|2解题思路

嗯这个斐波那契很麻烦,可以考虑一些用特征方程1xx2=0,可以得到斐波那契的通项公式

Fbi(n)=(5+12)n(512)n5

为了方便上面5±12分别记为X0,X1
那么如果设ci=X0ai,di=X1ai的话我们要求的就是

i=1kj=ikz=ijczi=1kj=ikz=ijdz5

这个好像看起来好维护一点,不过首先我们要解决这个5的问题,因为其实5在模998244353意义下是没有值的,我们不能直接用二次剩余带入数字。

考虑维护一个类似于多项式的东西,每个数字记为二元组(a,b)=a5+b。加减乘都很好搞,除法的话需要推导一下,

1a5+b=c5+d

5ac+5(ad+cb)+bd=1

5ac+bd=1,ad+cb=0

解出来

c=ab25a2,d=bb25a2

这样四则运算都搞定了,可以开始考虑如何在LCT上面维护了。

类似线段树的,设pro表示所有数乘积,pre/suf表示所有前/后缀乘积和,ans表示我们维护的答案,那么就可以合并两个东西了。LCT维护的时候顺便把单个的节点也合并进去就好了。

然后还剩下一个最麻烦的东西就是树链修改的时候我们需要快速算出连续xu的信息。
pro很好搞就是uxsufpre就是一个简单的等比数列求和,上通项公式就好了。
ans比较麻烦,考虑每个ui的个数答案就是

i=1xui(xi+1)=(x+1)i=1xuii=1xuii

(x+1)ux+1uu1xux+1uxuu1u1

这样就可以在log时间复杂度以内合并了。

然后答案0次项一定是0的,所以输出5的项就好了。
时间复杂度O(nlog2n)


1|3code

#include<cstdio> #include<cstring> #include<algorithm> #include<stack> #define ll long long using namespace std; const ll P=998244353,N=1e5+10; struct node{ ll a,b;//a带√5 node(ll aa=0,ll bb=0) {a=aa;b=bb;return;} }; ll power(ll x,ll b=P-2){ ll ans=1;x%=P; while(b){ if(b&1)ans=ans*x%P; x=x*x%P;b>>=1; } return ans; } const node X((P+1)/2,(P+1)/2); node operator+(node x,node y) {return node((x.a+y.a)%P,(x.b+y.b)%P);} node operator-(node x,node y) {return node((x.a-y.a)%P,(x.b-y.b)%P);} node operator*(node x,node y) {return node((x.a*y.b+x.b*y.a)%P,(x.b*y.b+5*x.a*y.a)%P);} node inv(node x){ ll tmp=power(x.b*x.b-5*x.a*x.a); return node(-x.a,x.b)*node(0,tmp); } node power(node x,ll b){ node ans(0,1); while(b){ if(b&1)ans=ans*x; x=x*x;b>>=1; } return ans; } struct Tnode{ node ans,pre,suf,pro; }; Tnode operator+(Tnode x,Tnode y){ Tnode w; w.ans=x.ans+y.ans+x.suf*y.pre; w.pre=x.pre+y.pre*x.pro; w.suf=y.suf+x.suf*y.pro; w.pro=x.pro*y.pro;return w; } struct SegTree{ ll fa[N],t[N][2],siz[N]; Tnode w[N];node v[N],lazy[N]; bool r[N],hlz[N];stack<ll> s; bool Nroot(ll x) {return fa[x]&&(t[fa[x]][0]==x||t[fa[x]][1]==x);} bool Direct(ll x) {return t[fa[x]][1]==x;} void Rev(ll x) {swap(t[x][0],t[x][1]);swap(w[x].pre,w[x].suf);r[x]^=1;return;} void PushUp(ll x){ siz[x]=siz[t[x][0]]+siz[t[x][1]]+1; w[x]=(Tnode){v[x],v[x],v[x],v[x]}; if(t[x][0])w[x]=w[t[x][0]]+w[x]; if(t[x][1])w[x]=w[x]+w[t[x][1]]; return; } void Updata(ll x,node u){ ll s=siz[x];lazy[x]=v[x]=u; node tmp=inv(node(0,1)-u); hlz[x]=1; w[x].pro=power(u,s); w[x].pre=w[x].suf=(u-w[x].pro*u)*tmp; w[x].ans=(node(0,s)-w[x].pre)*u*tmp; return; } void PushDown(ll x){ if(hlz[x]){ if(t[x][0])Updata(t[x][0],lazy[x]); if(t[x][1])Updata(t[x][1],lazy[x]); hlz[x]=0; } if(!r[x])return; Rev(t[x][0]);Rev(t[x][1]); r[x]=0;return; } void Rotate(ll x){ ll y=fa[x],z=fa[y]; ll xs=Direct(x),ys=Direct(y); ll w=t[x][xs^1]; if(Nroot(y))t[z][ys]=x; t[x][xs^1]=y;t[y][xs]=w; if(w)fa[w]=y;fa[y]=x;fa[x]=z; PushUp(y);PushUp(x);return; } void Splay(ll x){ ll y=x;s.push(x); while(Nroot(y))y=fa[y],s.push(y); while(!s.empty())PushDown(s.top()),s.pop(); while(Nroot(x)){ ll y=fa[x]; if(!Nroot(y))Rotate(x); else if(Direct(x)==Direct(y)) Rotate(y),Rotate(x); else Rotate(x),Rotate(x); } return; } void Access(ll x){ for(ll y=0;x;y=x,x=fa[x]) Splay(x),t[x][1]=y,PushUp(x); return; } void MakeRoot(ll x) {Access(x);Splay(x);Rev(x);return;} void Link(ll x,ll y){ MakeRoot(1);Access(x);Splay(x); fa[t[x][0]]=0;t[x][0]=0;PushUp(x); fa[x]=y;return; } ll Split(ll x,ll y){ MakeRoot(x);Access(y);Splay(y); return (w[y].ans.a+P)%P*2%P; } void Change(ll x,ll y,node val){ MakeRoot(x);Access(y);Splay(y); Updata(y,val);return; } }T; ll n,m; signed main() { // freopen("fibonacci.in","r",stdin); // freopen("fibonacci.out","w",stdout); scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;i++){ ll x;scanf("%lld",&x); T.v[i]=power(X,x); T.PushUp(i); } for(ll i=2;i<=n;i++) scanf("%lld",&T.fa[i]); while(m--){ ll op,u,v,w; scanf("%lld",&op); if(op==1){ scanf("%lld%lld",&u,&v); T.Link(u,v); } else if(op==2){ scanf("%lld%lld%lld",&u,&v,&w); T.Change(u,v,power(X,w)); } else if(op==3){ scanf("%lld",&u); printf("%lld\n",T.Split(u,u)); } else if(op==4){ scanf("%lld%lld",&u,&v); printf("%lld\n",T.Split(u,v)); } } return 0; }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/14453801.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(128)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示