题目大意
题解
这道题的难度大概是这次提高组最难的一题了,考试时一直在想线段树合并,发现时间过不了以后就放弃打了暴力。
关于这道题,可以容易发现,对于乘法操作,直接最后乘上即可,对于加法操作,只需要将加的数乘上后面乘的数就行了。
所以可以得出一个做法:
- 首先可以考虑反着做一次拓扑排序,求出每次操作中包含了多少次乘法操作。
- 然后从后往前扫一遍所有询问的操作,将乘法操作统计一下。
- 对于加法操作,发现乘上多少意味着重复了多少次这个操作,然后可以记录一下每个操作后面要经过多少次操作,然后再来一个拓扑排序,处理和一个加法操作同级的操作中后面要进行的乘法操作。
然后这道题就做完了。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 200000
#define M 2000000
#define ll long long
#define mo 998244353
using namespace std;
ll n,a[N],m,opt,Q,q[N],qp[N],num,p[N],sum;
ll i,j,k,data[N],x,y,val,tot;
struct edge{
ll to,next;
}e[M],ep[M];
struct node{
ll mul,add[3],out,in,times;
}f[N];
void insert(ll x,ll y){
tot++;
e[tot].to=y;
e[tot].next=q[x];
q[x]=tot;
}
void insertp(ll x,ll y){
tot++;
ep[tot].to=y;
ep[tot].next=qp[x];
qp[x]=tot;
}
int main(){
freopen("call.in","r",stdin);
freopen("call.out","w",stdout);
scanf("%lld",&n);
for (i=1;i<=n;i++) scanf("%lld",&a[i]);
scanf("%lld",&m);
for (i=1;i<=m;i++){
scanf("%lld",&opt);
f[i].mul=1;
if (opt==1)
scanf("%lld%lld",&f[i].add[1],&f[i].add[2]);
if (opt==2)
scanf("%lld",&f[i].mul);
if (opt==3){
scanf("%lld",&num);
for (j=1;j<=num;j++){
scanf("%lld",&x);
insert(i,x);
insertp(x,i);
f[x].in++;
f[i].out++;
}
}
}
j=0;
for (i=1;i<=m;i++)
if (!f[i].out){
j++;
data[j]=i;
}
i=0;
while (i<j){
i++;
x=data[i];
for (k=qp[x];k;k=ep[k].next){
y=ep[k].to;
f[y].mul=f[y].mul*f[x].mul%mo;
f[y].out--;
if (!f[y].out){
j++;
data[j]=y;
}
}
}
scanf("%lld",&Q);
val=1;
for (i=1;i<=Q;i++)
scanf("%lld",&p[i]);
for (i=Q;i>=1;i--){
x=p[i];
f[x].times=(f[x].times+val)%mo;
val=val*f[x].mul%mo;
}
for (i=1;i<=n;i++) a[i]=a[i]*val%mo;
j=0;
for (i=1;i<=m;i++)
if (!f[i].in){
j++;
data[j]=i;
}
i=0;
while (i<j){
i++;
x=data[i];
sum=f[x].times;
for (k=q[x];k;k=e[k].next){
y=e[k].to;
f[y].times=(f[y].times+sum)%mo;
sum=sum*f[y].mul%mo;
f[y].in--;
if (!f[y].in){
j++;
data[j]=y;
}
}
}
for (i=1;i<=m;i++)
if (f[i].add[1]) a[f[i].add[1]]=(a[f[i].add[1]]+f[i].times*f[i].add[2]%mo)%mo;
for (i=1;i<=n;i++) printf("%lld ",a[i]);
printf("
");
return 0;
}