【题解】 P7077 [CSP-S2020] 函数调用(拓扑排序)

发布时间 2023-08-27 16:02:09作者: Ian8877

题意

题目给定了一个长度为\(n\)序列\(a\)\(m\)个操作,操作一共有3种:
1.给定\(x,y\),使\(a_x\)增加\(y\)
2.给定\(x\),使\(a\)中所有数全部乘上\(x\)
3.给出k个数\(c_1,c_2,...,c_k\),表示这个操作的任务是按照先后顺序执行编号为\(c_1,c_2,...,c_k\)\(k\)的操作。
最后,题目相当于给出了一个编号为0的3操作,要求求出将操作执行完之后,变化完的\(a\)序列。
题目保证了对于每一个3操作不会回到自己。

分析

一开始看到几种操作,就会让人往数据结构方向上想,有点类似于线段树2,不过复杂度显然爆炸,只能另寻他法。
我们看到“不会回到自己”,就可以联想到可以构建一个DAG(有向无环图)。但是题目仍然复杂,于是我们可以开始先尝试一下将操作种数减少。

1.只有第一种:很明显,直接加上就完事了。

2.只有第一,三种:我们发现,在一个有向无环图上,我们可以轻松地将一个操作执行的次数\(cnt\)通过拓扑排序递推出来,最后对于每一个\(a_i\),我们把作用于第\(i\)位上的所有操作\(j\)的增加值乘上\(cnt\),再加回\(a_i\)即可。

3.只有第二,三种:此时,我们的任务就是把整个序列乘上的次数求出,其实也相当于对每个2操作求出操作次数\(cnt\),再将所有的\(x^{cnt}\)全部乘起来,最后将原序列的\(a_i\)乘上就可以了。

4.有第一,二种:现在每一种操作都只会执行一次,我们会看到,对于一个数\(a_i\),它最后的值会乘上所有二操作的总乘积再加上一操作的贡献,而每个一操作的贡献就会为它后面所有二操作的总乘积乘上它的\(y\),从操作序列倒推即可。

最后,通过一系列的分类讨论,我们终于要将三种操作同时讨论起来了。首先我们会看到,一个操作节点\(i\)的操作次数\(cnt\)会由到达它的节点决定。具体地说,现在在操作\(i\),它的子节点有\(c_1,c_2,....,c_k\),现在我们判断其对\(c_j\)的贡献,那么\(cnt_{c_j}\)会加上\(cnt_i\)乘上\(c_{j+1},c_{j+2},...,c_k\)之中会执行的操作二的总乘积,原因是此时,\(cnt_i\)决定了到达所有\(c_1,c_2,...,c_k\)的次数,它已经包含了\(cnt_i\)之外二操作的影响,而在\(i\)之中,\(j\)之后执行二操作填补了剩下对\(j\)的影响。

其实也就是说,我们对于每一个操作节点\(i\)可以维护两个量\(cnt_i,mul_i\),分别表示实际操作次数与其中二操作总乘积。
而现在\(i\)节点对子节点\(c_j\)的贡献可以写成\(cnt_{c_j}+=cnt_i*\prod_{x=j+1}^ k mul_{c_x}\)

最后一步我们仿照第二种讨论处理方式就可以将原问题解决了。

实现

我们首先将图建出来,建议使用vector存图,方便后面对顺序的控制。

然后跑一编记忆化搜索,将每一个节点的\(mul\)仿照第三种处理方式求出。

ll dfs(int now) {
	if(bj[now]) return mul[now];
	bj[now]=1;
	if(A[now].ti==2) mul[now]=A[now].vi;
	else mul[now]=1;
	for(vector<int> :: iterator it=g[now].begin();it!=g[now].end();it++) {
		int st=*it;
		mul[now]=mul[now]*dfs(st)%mod;
	}
	return mul[now];
}

接下来我们将每个节点的入度\(in\)求出,因为有一些操作并不需要使用,所以不可以一开始存图时处理,当我们处理完\(bj\)后,我们就会知道哪些点需要使用,很方便就完成了\(in\)的求值。

int in[maxn];
void build() {
	for(int i=0;i<=m;i++) {
		if(bj[i]) for(vector<int> :: iterator it=g[i].begin();it!=g[i].end();it++) in[*it]++;
	}
}

然后我们就可以开始愉快地进行拓扑排序了,根据上述提到的转移方式,同时我们遍历时应从后往前遍历,方便把后缀积求出。

void top_sort() {
	queue<int> que; que.push(0);
	cnt[0]=1;
	while(!que.empty()) {
		int now=que.front(); que.pop();
		ll mull=1;
		if(g[now].empty()) continue;
		for(vector<int> :: iterator it=--g[now].end();;it--) {
			int st=*it;
			cnt[st]=(cnt[st]+cnt[now]*mull%mod)%mod;
			mull=mull*mul[st]%mod;
			in[st]--; if(!in[st]) que.push(st);
			if(it==g[now].begin()) break;
		}
	}
}

最后的最后只需要轻松地求和就可以了,下面把AC代码一并奉上:

#include<algorithm>
#include<stdio.h>
#include<vector>
#include<queue>
#define maxn 100005
#define ll long long
using namespace std;
int n,m,q;
ll a[maxn],mul[maxn],bj[maxn];
ll mod=998244353;
struct ope {
	int ti,pi;
	ll vi;
} A[maxn];
vector<int> g[maxn];
ll dfs(int now) {
	if(bj[now]) return mul[now];
	bj[now]=1;
	if(A[now].ti==2) mul[now]=A[now].vi;
	else mul[now]=1;
	for(vector<int> :: iterator it=g[now].begin();it!=g[now].end();it++) {
		int st=*it;
		mul[now]=mul[now]*dfs(st)%mod;
	}
	return mul[now];
}
int in[maxn];
void build() {
	for(int i=0;i<=m;i++) {
		if(bj[i]) for(vector<int> :: iterator it=g[i].begin();it!=g[i].end();it++) in[*it]++;
	}
}
ll cnt[maxn];
void top_sort() {
	queue<int> que; que.push(0);
	cnt[0]=1;
	while(!que.empty()) {
		int now=que.front(); que.pop();
		ll mull=1;
		if(g[now].empty()) continue;
		for(vector<int> :: iterator it=--g[now].end();;it--) {
			int st=*it;
			cnt[st]=(cnt[st]+cnt[now]*mull%mod)%mod;
			mull=mull*mul[st]%mod;
			in[st]--; if(!in[st]) que.push(st);
			if(it==g[now].begin()) break;
		}
	}
}
void fix() {
	for(int i=1;i<=n;i++) a[i]=a[i]*mul[0]%mod; 
	for(int i=1;i<=m;i++) {
		if(A[i].ti==1) a[A[i].pi]=(a[A[i].pi]+A[i].vi*cnt[i]%mod)%mod;
	}
	for(int i=1;i<=n;i++) printf("%lld ",a[i]);
}
int main() {
	freopen("P7077.in","r",stdin);
	freopen("P7077.out","w",stdout);
	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",&A[i].ti);
		if(A[i].ti==1) scanf("%d%lld",&A[i].pi,&A[i].vi);			
		else if(A[i].ti==2) scanf("%lld",&A[i].vi);
		else {
			int c; scanf("%d",&c);
			for(int j=1;j<=c;j++) {
				int v; scanf("%d",&v); 
				g[i].push_back(v);
			}
		}
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++) {
		int v; scanf("%d",&v);
		g[0].push_back(v); 
	}
	dfs(0);
	build();
	top_sort();
	fix();
	return 0;
}

总结

一道好题,想了大概有两天了,发现其实只需要慢慢地分类讨论,正解也不是那么复杂。

THE END.