思路
考场上只花(10min)打了个(10pts)递归暴力跑路了。其实一眼看出是线段树2的板子,但是由于只剩半小时而且还没打(T4),所以果断放弃了。
其实这个题跟线段树完全没有关系,因为线段树最强的一点就是可以一边修改一边查询,但是本题只需要输出最后结果就行了,所以显然没有发挥出线段树的长处。然后仔细想了一个小时,思考出了个寂寞,果断看题解。看了五个小时终于看懂了,非常感动。
心态崩了,所以不想写思路,那些思路虽然已经深入我的脑海,但是我确实是不懂,只能把看懂的写在注释里。
最重要的思路其实就是倒着处理吧,因为只有先加后乘,乘上的数才能给加贡献,所以打标记的时候一定要倒着来。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1000005,mod=998244353;
int n,m,Q,k;
int in[N],f[N],op[N],p[N],qry[N];//f数组代表若第i个函数是类型1函数,则对第i个函数的加标记的贡献的倍数
ll a[N],mu=1,mul[N],w[N],add[N];//mul数组记录对全局乘标记的贡献,mu就是全局乘标记,add数组记录最后a数组乘完全局乘标记之后需要加上的数
bool vis[N];
vector<int> h[N];
queue<int> q;
void dfs(int num){//有点记搜的思想,先预处理所有的函数
vis[num]=1;
if(op[num]==2){
mul[num]=w[num];
}
else{
mul[num]=1;
}
for(int i=0;i<h[num].size();i++){
int to=h[num][i];
if(!vis[to]) dfs(to);
mul[num]=mul[num]*mul[to]%mod;//更新一遍
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d",&op[i]);
if(op[i]==1){
scanf("%d%d",&p[i],&w[i]);
}
if(op[i]==2){
scanf("%d",&w[i]);
}
if(op[i]==3){
scanf("%d",&k);
for(int j=1;j<=k;j++){
int numm;
scanf("%d",&numm);
h[i].push_back(numm);
in[numm]++;//为拓扑排序做准备
}
}
}
for(int i=1;i<=m;i++){
if(!in[i]&&!vis[i]) dfs(i);//如果在其他函数内就不用单独再预处理一遍了,直接从入度为0的点开始dfs即可
}
scanf("%d",&Q);
for(int i=1;i<=Q;i++){
scanf("%d",&qry[i]);
}
for(int i=Q;i>=1;i--){
if(op[qry[i]]==1){
f[qry[i]]=(f[qry[i]]+mu)%mod;
}
if(op[qry[i]]==2){
mu=(w[qry[i]]*mu)%mod;
}
if(op[qry[i]]==3){
f[qry[i]]=(f[qry[i]]+mu)%mod;
mu=(mul[qry[i]]*mu)%mod;//三种函数分别对全局的影响
}
}
for(int i=1;i<=Q;i++){
if(!in[i]) q.push(i);
}
while(!q.empty()){
int x=q.front();
q.pop();
if(op[x]==1) add[p[x]]=(add[p[x]]+f[x]*w[x]%mod)%mod;//这里入队的函数一定不可能再被3类型的函数更新了,因为它的入度已经是0了,所以可以直接更新最后的结果了
for(int i=h[x].size()-1;i>=0;i--){//这里也要倒着处理
int to=h[x][i];
in[to]--;if(!in[to]) q.push(to);
f[to]=(f[to]+f[x])%mod,f[x]=(f[x]*mul[to])%mod;
}
}
for(int i=1;i<=n;i++){
printf("%lld ",(a[i]*mu+add[i])%mod);
}
return 0;
}