首先考虑没有 3 操作的情况,显然有线段树的 \(O(n\log n)\) 做法,但是另外有一种 \(O(n)\) 做法:因为 2 操作是全局乘所以我们完全可以统计出全局乘了多少然后直接往 \(a_i\) 上乘,至于 1 操作,其加上的数会受到其后面的 2 操作的影响但是前面的不会,据此考虑倒序处理,对于每一个 1 操作我们算出后面 2 操作总共乘了多少就知道这个 1 操作到底对指定位置有多少贡献。
现在考虑加入 3 操作的影响,注意到如果 3 操作向其调用的函数连边那么函数关系会构成一张 DAG,对于指定的 \(q\) 个调用操作,建立超源 0 向其连单向边,考虑在反向图上使用一次拓扑排序求出每个操作执行之后每个点会使得全局乘上多少数,记作 \(Mul_x\)。
接下来考虑在正向图上做一次拓扑排序求出每个 1 操作最后会被乘多少次,设 \(cnt_x\) 表示当前操作会被乘多少次(注意 \(cnt_x\) 只对 1 操作有意义,但是每个点都需要记录以保证正确转移,初值 \(cnt_0=1\) 别的都是 0),注意到每一个操作只会被在其后面的所有 2 操作影响,所以我们需要倒序遍历每个 3 操作的操作序列,比如说如果一个 3 操作调用序列为 2 3 4
那么这次拓扑排序需要以 4 3 2
的顺序遍历,这样才能保证倒序处理,考虑记录一个 \(tag\) 表示倒序遍历时总共乘了多少也就是当前 3 操作内部乘上了多少,这样可以直接记录到前面操作的 \(cnt\) 上,至于整个 3 操作后面对当前 1 操作的贡献,直接从 3 操作往下传即可。
Code:
/*
========= Plozia =========
Author:Plozia
Problem:P7077 [CSP-S2020] 函数调用
Date:2022/10/12
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
using std::queue;
const int MAXN = 1e5 + 5, MAXM = 1e6 + 5;
const LL P = 998244353;
int n, m, opt[MAXN], pos[MAXN], q, d[MAXN];
LL Mul[MAXN], a[MAXN], val[MAXN], cnt[MAXN];
struct Graph
{
int Head[MAXN], cntEdge, In[MAXN]; struct node { int To, Next; } Edge[MAXM + MAXN];
void add(int x, int y) { ++cntEdge; Edge[cntEdge] = (node){y, Head[x]}; Head[x] = cntEdge; }
}og, rg;
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
void Topsort1()
{
queue <int> q; for (int i = 0; i <= m; ++i) if (rg.In[i] == 0) q.push(i);
while (!q.empty())
{
int x = q.front(); q.pop();
for (int i = rg.Head[x]; i; i = rg.Edge[i].Next)
{
int u = rg.Edge[i].To; --rg.In[u]; if (rg.In[u] == 0) q.push(u);
Mul[u] = Mul[u] * Mul[x] % P;
}
}
}
void Topsort2()
{
queue <int> q; for (int i = 0; i <= m; ++i) if (og.In[i] == 0) q.push(i);
while (!q.empty())
{
int x = q.front(); q.pop(); LL tag = 1;
for (int i = og.Head[x]; i; i = og.Edge[i].Next)
{
int u = og.Edge[i].To; --og.In[u]; if (og.In[u] == 0) q.push(u);
cnt[u] = (cnt[u] + cnt[x] * tag % P) % P; tag = tag * Mul[u] % P;
}
}
}
int main()
{
n = Read(); for (int i = 1; i <= n; ++i) a[i] = Read();
m = Read(); Mul[0] = cnt[0] = 1;
for (int i = 1; i <= m; ++i)
{
opt[i] = Read(); Mul[i] = 1;
if (opt[i] == 1) { pos[i] = Read(), val[i] = Read(); }
if (opt[i] == 2) { Mul[i] = Read(); }
if (opt[i] == 3)
{
int k = Read();
for (int j = 1; j <= k; ++j) { int tmp = Read(); og.add(i, tmp); rg.add(tmp, i); ++og.In[tmp]; ++rg.In[i]; }
}
}
q = Read(); for (int i = 1; i <= q; ++i) d[i] = Read();
for (int i = 1; i <= q; ++i) { og.add(0, d[i]); rg.add(d[i], 0); ++og.In[d[i]]; ++rg.In[0]; }
Topsort1(); Topsort2();
for (int i = 1; i <= n; ++i) a[i] = a[i] * Mul[0] % P;
for (int i = 1; i <= m; ++i)
if (opt[i] == 1) a[pos[i]] = (a[pos[i]] + cnt[i] * val[i] % P) % P;
for (int i = 1; i <= n; ++i) printf("%lld%c", a[i], " \n"[i == n]); return 0;
}
标签:cnt,ch,int,题解,函数调用,MAXN,操作,P7077,倒序
From: https://www.cnblogs.com/Plozia/p/16788400.html