首页 > 其他分享 >【题解】 P7077 [CSP-S2020] 函数调用(拓扑排序)

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

时间:2023-08-27 16:12:17浏览次数:48  
标签:cnt int 题解 函数调用 st S2020 操作 mul now

题意

题目给定了一个长度为\(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.

标签:cnt,int,题解,函数调用,st,S2020,操作,mul,now
From: https://www.cnblogs.com/1n87/p/17660385.html

相关文章

  • P2049 魔术棋子题解
    思路设\(f_{i,j,k}\)表示从原点走到\((i,j)\)模\(m\)后的乘积为\(k\)的方案数。状态转移:\(f_{i,j,ka_{i,j}\bmodm}=f_{i-1,j,k}+f_{i,j-1,k}\)统计答案:\(f_{n,n,k}\)。代码#include<bits/stdc++.h>usingnamespacestd;constintN=110......
  • CSP-S2020初赛易错题解析
    二.1.4.将第14行的 d[i]<d[j] 改为 d[i]!=d[j],程序输出不会改变。()答案:正确解析:因为双层for会遍历所有情况,所以输出不会改变 2.4.当输入的 d[i]d[i] 是严格单调递减序列时,第17行的 swap 平均执行次数是()A.O(n^2) B.O(n) C.O(nlogn) D.O(logn)正......
  • CSP-J2019初赛易错题解析
    7.把 8 个同样的球放在 5 个同样的袋子里,允许有的袋子空着不放,问共有多少种不同的分法?()提示:如果 8 个球都放在一个袋子里,无论是哪个袋子,都只算同一种分法。A.22B.24 C.18 D.20正解:使用枚举法,枚举所有合法情况,共18种 ......
  • P1385 密令题解
    思路我们发现两种操作都不会影响字符之和。考虑动态规划,设\(f_{i,j}\)表示在前\(i\)位,可以达到和为\(j\)的方案数。有\(f_{i,j}=\sum\limits_{k=0}^{25}f_{i-1,j-k}\)。最后记得\(-1\),表示去除原始字符串。代码#include<bits/stdc++.h>#defineintl......
  • CSP-S2019初赛易错题解析
    一.6.由数字 1,1,2,4,8,8 所组成的不同的 4 位数的个数是()A.104  B. 102  C. 98  D. 100错误原因:遗漏答案正解:使用穷举法,第一种ABCD型,共有A(4,4)=24种,第二种AABC型,共有A(4,2)*C(3,2)*2=72种,第三种AABB型,共有6种,总共是102种。 8.G 是一个非连通无向图(......
  • AT_agc030_d [AGC030D] Inversion Sum 题解
    AT_agc030_d[AGC030D]InversionSum题解题目大意给你一个长度为\(n\)的数列,然后给你\(q\)次交换操作,你每次可以选择操作或者不操作,问所有情况下逆序对的总和。(\(n,q\le3000\))分析很容易想到\(dp\),但是发现不好直接算方案。所以我们用一个小技巧,将求方案数转化为求......
  • [ABC317G] Rearranging 题解
    取自我的洛谷博客:https://www.luogu.com.cn/blog/SunnyYuan/solution-at-abc317-g借鉴了官方题解思路。思路首先我们要建立一个二分图。对于输入的\(a_{i,j}\),我们可以连接左侧的\(i\)和右侧的\(a_{i,j}\)。比如样例\(1\):注意:左边的\(1,2,3\)和右边的\(1,2......
  • 「TAOI-2」Ciallo~(∠・ω< )⌒★ 题解
    「TAOI-2」Ciallo~(∠・ω<)⌒★题解不难发现,答案可以分成两种:整段的中间删一点,两端凑一起的考虑分开计算贡献。如果\(s\)中存在子串等于\(t\),那么自然,可以删左边的任何一段,或者右边的任何一段。不妨设子串开始的位置为\(i\),于是其贡献为\((1+2+\cdots+i......
  • YACS 2023年8月月赛 甲组 T2 直线整点 题解
    简单题,先二分出直线上$x$最小的点使得这个点在矩形内。然后不断跳,直到遇到整点。(实际上要用扩欧,但初测能过于是就偷个懒没写) 接着不断跳直到不符合条件。先$\sqrt{V}$个跳一下,跳完后再一个一个跳就不用写二分了多好。代码:#include<iostream>#defineintlonglongusi......
  • UVA908[Re-connecting Computer Sites]题解
    原题1.题意分析题意就是给你很多组数,对于每组数,有三组小数据。第一组小数据先输入一个n表示顶点数,然后再输入n-1条边表示初始边数。其它组小数据先输入一个数k,表示增加的边的数量,然后再输入k条边,表示增加的边。在输入第二组小数据时,要先把边清空,重新输入,但是边的数量不变。2.做......