[CSP-S2020]函数调用 题解
看到数据范围里有“函数调用关系构成一棵树”的措辞,很容易想到对于第三类函数进行连边。虽然没有这个提示也很好想到。由于不存在调用自己的情况,连边后建成的图是一个 DAG ,只有代表第三类函数的节点有子节点。
直接模拟显然会爆炸,考虑计算每个函数执行的次数,然后再一起执行。但是因为操作有加有乘,还要考虑到顺序的影响,复杂度依然降不下来。
发现乘的操作只有全局乘,对所有数的影响相同,考虑把乘的操作进行转化。显然,对于乘 $x$ 的第二类函数,相当于将它之前的所有第一类函数调用了 $x$ 遍。因此,对于每一次调用的第一类函数,只要知道在它之后调用的第二类函数的情况,就可以计算出它对最终状态的影响,即相当于调用了几遍。
想到在建成的图上进行拓排,当代表某一函数的节点入队时,最后一次调用它之前的所有函数都已经处理结束了,这样就可以进行信息的汇总。
根据前面的分析,对于每一个第一类函数,要知道在它之后调用的第二类函数的情况,因此想到先倒着拓排一遍,统计每个节点之后调用的第二类函数的情况,即设 $mul_i$ 表示调用 $i$ 函数后总共要乘上 $mul_i$ 。那么,初值的话,对于第一类和第三类函数, $mul_i=1$ ;对于第二类函数, $mul_i=V_i$ 。
接下来就要知道每种第一类函数的调用次数,这个次数应该由根节点下传。从图上来看,若不考虑第二类函数,即有多少条路径可以到达代表该函数的节点;那么考虑进第二类函数,就要对于每条路径乘上路径上调用第二类函数的情况。但除此之外还要统计在它之前执行的兄弟节点的第二类函数的乘的操作。
显然这个算法的复杂度是线性的,$O(n+m+Q)$ 。
实现的时候,可以将最开始的依次调用 $Q$ 个函数看作一个第三类函数,然后以代表这个第三类函数的节点作为根节点进行下传,即拓排最开始唯一入队的点。因为要正反进行两次拓排,可以先进行一次拓排,预处理出拓扑序,然后按顺序处理即可。
//FJ-00445 //NOIP2020 RP++ #include<iostream> #include<cstdio> #include<queue> #define ll long long using namespace std; const int N=1e5+100,M=1e6+100; const ll P=998244353; int n,m,Q,tot,cnt; int b[N],t[N],ind[N]; int head[N],ver[2*M],Next[2*M]; ll ans; ll a[N],u[N],v[N],sum[N],mul[N]; void add(int x,int y) { ver[++tot]=y,Next[tot]=head[x],head[x]=tot,ind[y]++; } void topusort() { queue<int> q; for(int i=1;i<=m+1;i++) if(!ind[i]) q.push(i); while(q.size()) { int x=q.front();q.pop(); b[++cnt]=x; for(int i=head[x];i;i=Next[i]) { int y=ver[i];ind[y]--; if(!ind[y]) q.push(y); } } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); scanf("%d",&m); for(int i=1,x,y;i<=m;i++) { scanf("%d",&t[i]); if(t[i]==1) scanf("%lld%lld",&u[i],&v[i]),mul[i]=1; if(t[i]==2) scanf("%lld",&u[i]),mul[i]=u[i]; if(t[i]==3) { scanf("%d",&x);mul[i]=1; for(int j=1;j<=x;j++) { scanf("%d",&y); add(i,y); } } } scanf("%d",&Q); for(int i=1,x;i<=Q;i++) { scanf("%d",&x); add(m+1,x); } topusort();sum[m+1]=mul[m+1]=1; for(int i=cnt;i;i--) { int x=b[i]; for(int j=head[x];j;j=Next[j]) { int y=ver[j]; mul[x]=mul[x]*mul[y]%P; } } for(int i=1;i<=cnt;i++) { int x=b[i];ll now=1; for(int j=head[x];j;j=Next[j]) { int y=ver[j]; sum[y]=(sum[y]+now*sum[x]%P)%P; now=now*mul[y]%P; } } for(int i=1;i<=n;i++) a[i]=a[i]*mul[m+1]%P; for(int i=1;i<=m+1;i++) if(t[i]==1) a[u[i]]=(a[u[i]]+v[i]*sum[i]%P)%P; for(int i=1;i<=n;i++) printf("%lld ",a[i]); return 0; }