1 条题解
-
0
题目要求我们维护三种操作:
- 单点加;
- 区间乘;
- 依次调用函数。
容易想到函数的调用关系构成了一张图,题目数据中的特殊性质“函数调用关系构成一棵树”和 都给出了提示,而题目保证不会出现递归函数则说明这是一张有向无环图即 DAG。
接下来考虑如何维护操作 1 和操作 2。
先考虑操作 2,比较简单,直接把所有区间乘全部乘起来即可。具体地,建一个反向图,做一遍拓扑把所有的乘数乘到根节点上。
再考虑操作 1。我们发现具体增加的数值比较难维护,因为在图中一个出度不为 0 的点可能有多个子节点。因此我们考虑维护加操作最终乘上的系数,这样就不用具体到某个加操作上了。
令 为 操作中加操作乘上的系数,则有 ,其中 为 当前子节点, 为 所有遍历过的子节点的乘数之积。拓扑跑一遍即可。
注:
- 为了方便,我们可以用一个虚拟源点(节点 0)连向最后操作序列中的每个操作。
- 题目要求依次执行函数,所以要用邻接表存图。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=1e5+10,mod=998244353; int n,p[N],v[N],d[N],m; LL mul[N],add[N],cnt[N],a[N]; vector<int> e1[N],e2[N];//e1为正向图,e2为反向图 void get_mul(){//乘数 queue<int> q; for(int i=0;i<=m;i++){ d[i]=e1[i].size(); if(!d[i]) q.push(i); } while(q.size()){ int t=q.front(); q.pop(); for(int j:e2[t]){ mul[j]=mul[j]*mul[t]%mod; if(!--d[j]) q.push(j); } } } void get_cnt(){//加操作系数 queue<int> q; for(int i=0;i<=m;i++){ d[i]=e2[i].size(); if(!d[i]) q.push(i); } while(q.size()){ int t=q.front(); q.pop(); LL ne=1; for(int i=e1[t].size()-1;i>=0;i--){ int j=e1[t][i]; cnt[j]=(cnt[j]+cnt[t]*ne)%mod; ne=ne*mul[j]%mod; if(!--d[j]) q.push(j); } } } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; cin>>m; for(int i=1;i<=m;i++){ mul[i]=1; int op; cin>>op; if(op==1) cin>>p[i]>>v[i]; else if(op==2) cin>>mul[i]; else{ int c; cin>>c; while(c--){ int g; cin>>g; e1[i].push_back(g); e2[g].push_back(i); } } } int q; cin>>q; while(q--){ int x; cin>>x; e1[0].push_back(x); e2[x].push_back(0); } mul[0]=cnt[0]=1; get_mul(),get_cnt(); for(int i=1;i<=n;i++) a[i]=a[i]*mul[0]%mod; for(int i=1;i<=m;i++) a[p[i]]=(a[p[i]]+cnt[i]*v[i])%mod; for(int i=1;i<=n;i++) cout<<a[i]<<" "; return 0; }
- 1
信息
- ID
- 2010
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 9
- 已通过
- 3
- 上传者