【拓扑序 时间倒流法】P7077 [CSP-S2020] 函数调用|省选-
本文涉及的知识点
C++图论 拓扑序
时间倒流法
P7077 [CSP-S2020] 函数调用
题目描述
函数是各种编程语言中一项重要的概念,借助函数,我们总可以将复杂的任务分解成一个个相对简单的子任务,直到细化为十分简单的基础操作,从而使代码的组织更加严密、更加有条理。然而,过多的函数调用也会导致额外的开销,影响程序的运行效率。
某数据库应用程序提供了若干函数用以维护数据。已知这些函数的功能可分为三类:
- 将数据中的指定元素加上一个值;
- 将数据中的每一个元素乘以一个相同值;
- 依次执行若干次函数调用,保证不会出现递归(即不会直接或间接地调用本身)。
在使用该数据库应用时,用户可一次性输入要调用的函数序列(一个函数可能被调用多次),在依次执行完序列中的函数后,系统中的数据被加以更新。某一天,小 A 在应用该数据库程序处理数据时遇到了困难:由于频繁而低效的函数调用,系统在执行操作时进入了无响应的状态,他只好强制结束了数据库程序。为了计算出正确数据,小 A 查阅了软件的文档,了解到每个函数的具体功能信息,现在他想请你根据这些信息帮他计算出更新后的数据应该是多少。
输入格式
第一行一个正整数 nnn,表示数据的个数。
第二行 nnn 个整数,第 iii 个整数表示下标为 iii 的数据的初始值为 aia_iai。
第三行一个正整数 mmm,表示数据库应用程序提供的函数个数。函数从 1∼m1 \sim m1∼m 编号。
接下来 mmm 行中,第 jjj(1≤j≤m1 \le j \le m1≤j≤m)行的第一个整数为 TjT_jTj,表示 jjj 号函数的类型:
- 若 Tj=1T_j = 1Tj=1,接下来两个整数 Pj,VjP_j, V_jPj,Vj 分别表示要执行加法的元素的下标及其增加的值;
- 若 Tj=2T_j = 2Tj=2,接下来一个整数 VjV_jVj 表示所有元素所乘的值;
- 若 Tj=3T_j = 3Tj=3,接下来一个正整数 CjC_jCj 表示 jjj 号函数要调用的函数个数,
随后 CjC_jCj 个整数 g1(j),g2(j),…,gCj(j)g^{(j)}_1, g^{(j)}_2, \ldots , g^{(j)}_{C_j}g1(j),g2(j),…,gCj(j) 依次表示其所调用的函数的编号。
第 m+4m + 4m+4 行一个正整数 QQQ,表示输入的函数操作序列长度。
第 m+5m + 5m+5 行 QQQ 个整数 fif_ifi,第 iii 个整数表示第 iii 个执行的函数的编号。
输出格式
一行 nnn 个用空格隔开的整数,按照下标 1∼n1 \sim n1∼n 的顺序,分别输出在执行完输入的函数序列后,数据库中每一个元素的值。答案对 998244353\boldsymbol{998244353}998244353 取模。
输入输出样例 #1
输入 #1
3
1 2 3
3
1 1 1
2 2
3 2 1 2
2
2 3
输出 #1
6 8 12
输入输出样例 #2
输入 #2
10
1 2 3 4 5 6 7 8 9 10
8
3 2 2 3
3 2 4 5
3 2 5 8
2 2
3 2 6 7
1 2 5
1 7 6
2 3
3
1 2 3
输出 #2
36 282 108 144 180 216 504 288 324 360
输入输出样例 #3
输入 #3
见附件中的 call/call3.in
输出 #3
见附件中的 call/call3.ans
说明/提示
【样例 #1 解释】
111 号函数功能为将 a1a_1a1 的值加一。222 号函数功能为所有元素乘 222。333 号函数将先调用 111 号函数,再调用 222 号函数。
最终的函数序列先执行 222 号函数,所有元素的值变为 2,4,62, 4, 62,4,6。
再执行 333 号函数时,先调用 111 号函数,所有元素的值变为 3,4,63, 4, 63,4,6。再调用 222 号函数,所有元素的值变为 6,8,126, 8, 126,8,12。
【数据范围】
测试点编号 | n,m,Q≤n, m, Q \len,m,Q≤ | ∑Cj\sum C_j∑Cj | 其他特殊限制 |
---|---|---|---|
1∼21 \sim 21∼2 | 100010001000 | =m−1= m - 1=m−1 | 函数调用关系构成一棵树 |
3∼43 \sim 43∼4 | 100010001000 | ≤100\le 100≤100 | 无 |
5∼65 \sim 65∼6 | 200002000020000 | ≤40000\le 40000≤40000 | 不含第 222 类函数或不含第 111 类函数 |
777 | 200002000020000 | =0= 0=0 | 无 |
8∼98 \sim 98∼9 | 200002000020000 | =m−1= m - 1=m−1 | 函数调用关系构成一棵树 |
10∼1110 \sim 1110∼11 | 200002000020000 | ≤2×105\le 2 \times 10^5≤2×105 | 无 |
12∼1312 \sim 1312∼13 | 10510^5105 | ≤2×105\le 2 \times 10^5≤2×105 | 不含第 222 类函数或不含第 111 类函数 |
141414 | 10510^5105 | =0= 0=0 | 无 |
15∼1615 \sim 1615∼16 | 10510^5105 | =m−1= m - 1=m−1 | 函数调用关系构成一棵树 |
17∼1817 \sim 1817∼18 | 10510^5105 | ≤5×105\le 5 \times 10^5≤5×105 | 无 |
19∼2019 \sim 2019∼20 | 10510^5105 | ≤106\le 10^6≤106 | 无 |
对于所有数据:0≤ai≤1040 \le a_i \le 10^40≤ai≤104,Tj∈{1,2,3}T_j \in \{1,2,3\}Tj∈{1,2,3},1≤Pj≤n1 \le P_j \le n1≤Pj≤n,0≤Vj≤1040 \le V_j \le 10^40≤Vj≤104,1≤gk(j)≤m1 \le g^{(j)}_k \le m1≤gk(j)≤m,1≤fi≤m1 \le f_i \le m1≤fi≤m。
拓扑序 时间倒流法
简化版:只有操作一操作二
依次执行n个操作ope,只有加法和乘法。如果i个操作是加法:b[i] 是要增加的数的下标,c[i]是要增加的值,d[i]是1。如果是乘法,b[i]=c[i]=0,d[i]等于要乘的数。suff[i] = Πk:0N−1d[k]\Pi_{k:0}^{N-1} d[k]Πk:0N−1d[k]
性质一:以任意顺序执行以下操作各一次:a[b[i]]+=c[i]∗d[i]a[b[i]] += c[i]*d[i]a[b[i]]+=c[i]∗d[i]
性质二:两个相邻的乘法操作可以合并。即d[i] *= d[i+1],删除ope[i+1]。
注意一:a[i]的初值,转化成加法。
注意二:逆序处理,方便计算suff。
操作三
f(i,j)⟺\iff⟺ 依次执行ope[i...j]ope[i...j]ope[i...j]
性质二:依次执行以下四个操作: f(i,j) 乘以 x1 f(i,j) 乘以x2 ⟺\iff⟺ 依次执行以下两个操作 f(i,j) 乘以x2+x2×x1x2 + x2 \times x1x2+x2×x1
拓扑序
加法和乘法没有孩子。
操作三i调用的函数是它的孩子,由于逆序处理,故neiBo[i] = {gc⋯g1g_c \cdots g_1gc⋯g1}
增加节点0,neiBo[0] = {f_{Q} \cdots f_1$}。
实现
vMul[u],u函数所有叶子节点d[i]之积,按拓扑序处理,Πv是u子节点vMul[v]\Pi_{v是u子节点}vMul[v]Πv是u子节点vMul[v]
need[0]=1
按拓扑序逆序处理各节点u:
iMul = need[u]
如果是叶子节点: a[b[u]] += c[u] $\times $ iMul
如果非叶子节点:
for(const auto& v : neiBo[u]){
need[v] += iMul;
iMul *= vMul[v]
}
代码
核心代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>#include <bitset>
using namespace std;template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {in >> pr.first >> pr.second;return in;
}template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t);return in;
}template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);return in;
}template<class T1, class T2, class T3, class T4,class T5 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4,T5>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t) >> get<4>(t);return in;
}template<class T1, class T2, class T3, class T4, class T5,class T6 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4, T5,T6>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t) >> get<4>(t) >>get<5>(t);return in;
}template<class T = int>
vector<T> Read() {int n;cin >> n;vector<T> ret(n);for (int i = 0; i < n; i++) {cin >> ret[i];}return ret;
}
template<class T = int>
vector<T> ReadNotNum() {vector<T> ret;T tmp;while (cin >> tmp) {ret.emplace_back(tmp);if ('\n' == cin.get()) { break; }}return ret;
}template<class T = int>
vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {cin >> ret[i];}return ret;
}template<int N = 1'000'000>
class COutBuff
{
public:COutBuff() {m_p = puffer;}template<class T>void write(T x) {int num[28], sp = 0;if (x < 0)*m_p++ = '-', x = -x;if (!x)*m_p++ = 48;while (x)num[++sp] = x % 10, x /= 10;while (sp)*m_p++ = num[sp--] + 48;AuotToFile();}void writestr(const char* sz) {strcpy(m_p, sz);m_p += strlen(sz);AuotToFile();}inline void write(char ch){*m_p++ = ch;AuotToFile();}inline void ToFile() {fwrite(puffer, 1, m_p - puffer, stdout);m_p = puffer;}~COutBuff() {ToFile();}
private:inline void AuotToFile() {if (m_p - puffer > N - 100) {ToFile();}}char puffer[N], * m_p;
};template<int N = 1'000'000>
class CInBuff
{
public:inline CInBuff() {}inline CInBuff<N>& operator>>(char& ch) {FileToBuf();ch = *S++;return *this;}inline CInBuff<N>& operator>>(int& val) {FileToBuf();int x(0), f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行 return *this;}inline CInBuff& operator>>(long long& val) {FileToBuf();long long x(0); int f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行return *this;}template<class T1, class T2>inline CInBuff& operator>>(pair<T1, T2>& val) {*this >> val.first >> val.second;return *this;}template<class T1, class T2, class T3>inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val);return *this;}template<class T1, class T2, class T3, class T4>inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);return *this;}template<class T = int>inline CInBuff& operator>>(vector<T>& val) {int n;*this >> n;val.resize(n);for (int i = 0; i < n; i++) {*this >> val[i];}return *this;}template<class T = int>vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {*this >> ret[i];}return ret;}template<class T = int>vector<T> Read() {vector<T> ret;*this >> ret;return ret;}
private:inline void FileToBuf() {const int canRead = m_iWritePos - (S - buffer);if (canRead >= 100) { return; }if (m_bFinish) { return; }for (int i = 0; i < canRead; i++){buffer[i] = S[i];//memcpy出错 }m_iWritePos = canRead;buffer[m_iWritePos] = 0;S = buffer;int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);if (readCnt <= 0) { m_bFinish = true; return; }m_iWritePos += readCnt;buffer[m_iWritePos] = 0;S = buffer;}int m_iWritePos = 0; bool m_bFinish = false;char buffer[N + 10], * S = buffer;
};class CNeiBo
{
public:static vector<vector<int>> Two(int n, const vector<pair<int, int>>& edges, bool bDirect, int iBase = 0){vector<vector<int>> vNeiBo(n);for (const auto& [i1, i2] : edges){vNeiBo[i1 - iBase].emplace_back(i2 - iBase);if (!bDirect){vNeiBo[i2 - iBase].emplace_back(i1 - iBase);}}return vNeiBo;}static vector<vector<int>> Two(int n, const vector<vector<int>>& edges, bool bDirect, int iBase = 0){vector<vector<int>> vNeiBo(n);for (const auto& v : edges){vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);if (!bDirect){vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);}}return vNeiBo;}static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0){vector<vector<std::pair<int, int>>> vNeiBo(n);for (const auto& v : edges){vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);if (!bDirect){vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);}}return vNeiBo;}static vector<vector<std::pair<int, int>>> Three(int n, const vector<tuple<int, int, int>>& edges, bool bDirect, int iBase = 0){vector<vector<std::pair<int, int>>> vNeiBo(n);for (const auto& [u, v, w] : edges){vNeiBo[u - iBase].emplace_back(v - iBase, w);if (!bDirect){vNeiBo[v - iBase].emplace_back(u - iBase, w);}}return vNeiBo;}static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat){vector<vector<int>> neiBo(neiBoMat.size());for (int i = 0; i < neiBoMat.size(); i++){for (int j = i + 1; j < neiBoMat.size(); j++){if (neiBoMat[i][j]){neiBo[i].emplace_back(j);neiBo[j].emplace_back(i);}}}return neiBo;}
};class CDGTopSort
{
public:template <class T = vector<int> >CDGTopSort(const vector<T>& vNeiBo) :m_vDeg(vNeiBo.size()), m_neiBo(vNeiBo) {const int N = vNeiBo.size();m_backNeiBo.resize(N);for (int cur = 0; cur < N; cur++){m_vDeg[cur] = vNeiBo[cur].size();for (const auto& next : vNeiBo[cur]){m_backNeiBo[next].emplace_back(cur);}}}void Init() {auto Add = [&](int i) {if (0 != m_vDeg[i]) { return; }m_que.emplace(i);};for (int i = 0; i < m_vDeg.size(); i++){Add(i);}while (m_que.size()){const int cur = m_que.front(); m_que.pop();if (!OnDo(cur)) { continue; }for (const auto& next : m_backNeiBo[cur]){m_vDeg[next]--;Add(next);}};}queue<int> m_que;vector<int> m_vDeg;vector<int> m_vSort;
protected:const vector<vector<int>>& m_neiBo;vector<vector<int>> m_backNeiBo;virtual bool OnDo(int cur) {m_vSort.emplace_back(cur);return true;};
};template<long long MOD = 1000000007, class T1 = int, class T2 = long long>
class C1097Int
{
public:C1097Int(T1 iData = 0) :m_iData(iData% MOD){}C1097Int(T2 llData) :m_iData(llData% MOD) {}C1097Int operator+(const C1097Int& o)const{return C1097Int(((T2)m_iData + o.m_iData) % MOD);}C1097Int& operator+=(const C1097Int& o){m_iData = ((T2)m_iData + o.m_iData) % MOD;return *this;}C1097Int& operator-=(const C1097Int& o){m_iData = ((T2)MOD + m_iData - o.m_iData) % MOD;return *this;}C1097Int operator-(const C1097Int& o)const{return C1097Int(((T2)MOD + m_iData - o.m_iData) % MOD);}C1097Int operator*(const C1097Int& o)const{return((T2)m_iData * o.m_iData) % MOD;}C1097Int& operator*=(const C1097Int& o){m_iData = ((T2)m_iData * o.m_iData) % MOD;return *this;}C1097Int operator/(const C1097Int& o)const{return *this * o.PowNegative1();}C1097Int& operator/=(const C1097Int& o){*this *= o.PowNegative1();return *this;}bool operator==(const C1097Int& o)const{return m_iData == o.m_iData;}bool operator<(const C1097Int& o)const{return m_iData < o.m_iData;}C1097Int pow(T2 n)const{C1097Int iRet = (T1)1, iCur = *this;while (n){if (n & 1){iRet *= iCur;}iCur *= iCur;n >>= 1;}return iRet;}C1097Int PowNegative1()const{return pow(MOD - 2);}T1 ToInt()const{return ((T2)m_iData + MOD) % MOD;}
private:T1 m_iData = 0;;
};class Solution {
public:vector<int> Ans(vector<int>& as, vector<vector<int>>& fun, vector<int>& que) {typedef C1097Int< 998244353> BI;vector<int> tmp(que.rbegin(), que.rend());que.swap(tmp);for (int i = 0; i < as.size(); i++) {fun.emplace_back(vector<int>{1, i + 1, as[i]});que.emplace_back(fun.size());}const int N = as.size(), M = fun.size(), NN = M + 1;vector<vector<int>> neiBo(NN);vector<int> b(NN);vector<BI> c(NN), vMul(NN, 1);neiBo[0] = que;for (int i = 0; i < M; i++) {if (1 == fun[i].front()) {b[i + 1] = fun[i][1] - 1;c[i + 1] = fun[i][2];}else if (2 == fun[i].front()) {vMul[i + 1] = fun[i][1];}else if (3 == fun[i].front()) {for (int j = fun[i].size() - 1; j >= 2; j--) {neiBo[1 + i].emplace_back(fun[i][j]);}}}CDGTopSort topSort(neiBo);topSort.Init();for (const auto& u : topSort.m_vSort) {for (const auto& v : neiBo[u]) {vMul[u] *= vMul[v];}}vector<BI> need(NN), biAns(N);need[0] = 1;for (const auto& u : vector<int>(topSort.m_vSort.rbegin(), topSort.m_vSort.rend())) {BI iMul = need[u];if (neiBo[u].empty()) {biAns[b[u]] += c[u] * iMul;continue;}for (const auto& v : neiBo[u]) {need[v] += iMul;iMul *= vMul[v];}}vector<int> ans(N);for (int i = 0; i < N; i++) {ans[i] = biAns[i].ToInt();}return ans;}
};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUGios::sync_with_stdio(0); cin.tie(nullptr); cout.tie(nullptr); int M ,kind,cnt;auto a = Read<int>();cin >> M ;vector<vector<int>> fun(M);for (int i = 0; i < M; i++) {cin >> kind >> cnt;if (1 == kind) { fun[i] = { 1,cnt,0 };cin >> fun[i].back();}else if (2 == kind) {fun[i] = { 2,cnt };}else {fun[i].resize(cnt + 2);fun[i].front() = 3;fun[i][1] = cnt;for (int j = 0; j < cnt; j++) {cin >> fun[i][2 + j];}}}auto que = Read<int>();
#ifdef _DEBUG//printf("N=%d",N);Out(a, ",a=");Out(fun, ",fun=");Out(que, ",que=");
#endif // DEBUGauto res = Solution().Ans(a,fun,que);for (const auto& i : res){cout << i << " ";}return 0;
}
单元测试
vector<int> a, que;vector<vector<int>> fun;TEST_METHOD(TestMethod01){a = { 1 }, fun = { {2,2},{1,1,3 } }, que = { 1,2 };auto res = Solution().Ans(a, fun, que);AssertV({ 5 }, res);}TEST_METHOD(TestMethod02){a = { 1 }, fun = { {2,2},{1,1,3 } }, que = { 2,1 };auto res = Solution().Ans(a, fun, que);AssertV({ 8 }, res);}TEST_METHOD(TestMethod11){a = { 1,2,3 }, fun = { {1,1,1},{2,2},{3,2,1,2} }, que = { 2,3 };auto res = Solution().Ans(a, fun, que);AssertV({6,8,12 }, res);}TEST_METHOD(TestMethod12){a = { 1,2,3,4,5,6,7,8,9,10 }, fun = { {3,2,2,3},{3,2,4,5},{3,2,5,8},{2,2},{3,2,6,7},{1,2,5},{1,7,6},{2,3} }, que = { 1,2,3 };auto res = Solution().Ans(a, fun, que);AssertV({ 36,282, 108, 144, 180 ,216 ,504 ,288, 324, 360 }, res);}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。