CSP-S2020 T3 函數調用


CSP-S2020 T3 函數調用

洛谷評測傳送門

題目描述

函數是各種編程語言中一項重要的概念,借助函數,我們總可以將復雜的任務分解成一個個相對簡單的子任務,直到細化為十分簡單的基礎操作,從而使代碼的組織更加嚴密、更加有條理。然而,過多的函數調用也會導致額外的開銷,影響程序的運行效率。

某數據庫應用程序提供了若干函數用以維護數據。已知這些函數的功能可分為三類:

  1. 將數據中的指定元素加上一個值;
  2. 將數據中的每一個元素乘以一個相同值;
  3. 依次執行若干次函數調用,保證不會出現遞歸(即不會直接或間接地調用本身)。

在使用該數據庫應用時,用戶可一次性輸入要調用的函數序列(一個函數可能被調用多次),在依次執行完序列中的函數后,系統中的數據被加以更新。某一天,小 A 在應用該數據庫程序處理數據時遇到了困難:由於頻繁而低效的函數調用,系統在執行操作時進入了無響應的狀態,他只好強制結束了數據庫程序。為了計算出正確數據,小 A 查閱了軟件的文檔,了解到每個函數的具體功能信息,現在他想請你根據這些信息幫他計算出更新后的數據應該是多少。

輸入格式

第一行一個正整數 nn,表示數據的個數。
第二行 nn 個整數,第 ii 個整數表示下標為 ii 的數據的初始值為 a_ia**i​。
第三行一個正整數 mm,表示數據庫應用程序提供的函數個數。函數從 1 \sim m1∼m 編號。
接下來 mm 行中,第 jj(1 \le j \le m1≤jm)行的第一個整數為 T_jT**j​,表示 jj 號函數的類型:

  1. 若 T_j = 1T**j=1,接下來兩個整數 P_j, V_jP**j,V**j 分別表示要執行加法的元素的下標及其增加的值;
  2. 若 T_j = 2T**j=2,接下來一個整數 V_jV**j 表示所有元素所乘的值;
  3. 若 T_j = 3T**j=3,接下來一個正整數 C_jC**j 表示 jj 號函數要調用的函數個數,
    隨后 C_jC**j​ 個整數 g^{(j)}_1, g^{(j)}2, \ldots , g^{(j)}{C_j}g1(j)​,g2(j)​,…,gCj​(j)​ 依次表示其所調用的函數的編號。

第 m + 4m+4 行一個正整數 QQ,表示輸入的函數操作序列長度。
第 m + 5m+5 行 QQ 個整數 f_if**i​,第 ii 個整數表示第 ii 個執行的函數的編號。

輸出格式

一行 nn 個用空格隔開的整數,按照下標 1 \sim n1∼n 的順序,分別輸出在執行完輸入的函數序列后,數據庫中每一個元素的值。答案對 \boldsymbol{998244353}998244353 取模。


題解:

考試之前特意總結的圖論模型的抽象。甚至還無限接近正解地壓了拓撲序上DP。結果來了一道這樣的題,還是不會。

太菜了。

考場策略就是個渣渣。本來以為多考了一年能夠好一些,結果還是渣渣。甚至還不如第一年去考。

太菜了。

太菜了。

真是太菜了。

題目中有很多地方暗示了要建圖來考慮解決這個問題。比如無遞歸,就暗示了沒有環。比如函數調用。調用的過程就是一張圖。比如部分分給的調用關系是一棵樹。

考場上時間真的不夠了。這道題甚至都沒有細細思考。就直接上手打了最裸的暴力,連線段樹架上去優化都沒加。真的沒有發揮到自己比其他人優秀的地方,光看代碼,給人的感覺就是這個人和普及選手甚至0基礎沒有任何的區別。

太菜了。

然后中間過程還爆。講過多少遍,還是忘記了。

太菜了。

真的只是緊張么?

太菜了。

總的來說,這道題是一張DAG。這是很顯然的。然后我們發現,不能簡單地按拓撲序架數據結構。因為這個兩種操作和兩種操作各自的順序就讓人很迷茫。

於是考慮加法和乘法之間的轉換。可以看出,乘法可以轉化成若干次加法。所以對於一次加法,其后面的乘法操作可以轉換成加幾次來賦給這個加法,作為這個加法的貢獻。

也就是,對於一個加法操作,其貢獻是后面所有乘法操作的積。

對於整張拓撲圖進行DP。統計后綴即。至於同一層的順序問題,在使用鏈式前向星存儲的時候,就是按順序存的了,所以自然也會按順序去遍歷。

然后對整張拓撲圖DP的時候可以把整張圖的遍歷順序用深搜序轉成序列問題。這樣倒序再來一遍DP。就保證了轉移的階段性,即統計出了所有加法得到的貢獻。

所以:

#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
char *p1,*p2,buf[100000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0,gx=1;
	char ch=nc();
	while(ch<48||ch>57)
		if(ch=='-')
			gx=-1,ch=nc();
	while(ch>=48&&ch<=57)
		x=x*10+ch-48,ch=nc();
	return x*gx;
}
const int maxn=1e6+5;
const int mod=998244353;
int n,m,q;
int pos[maxn],val[maxn<<1],id[maxn],opt[maxn<<1],cnt;
int tot,head[maxn<<1],nxt[maxn<<1],to[maxn<<1],lazy[maxn<<1];
int gx[maxn],dp[maxn],ans[maxn];
void add(int x,int y)
{
	to[++tot]=y;
	lazy[tot]=1;
	nxt[tot]=head[x];
	head[x]=tot;
}
int dfs(int x)
{
	if(~gx[x])
		return gx[x];
	if(opt[x]==1)
		gx[x]=1;
	else if(opt[x]==2)
		gx[x]=val[x];
	else
	{
		gx[x]=1;
		for(int i=head[x];i;i=nxt[i])
		{
			lazy[i]=gx[x];
			gx[x]=(gx[x]*1ll*dfs(to[i]))%mod;
		}
	}
	id[++cnt]=x;
	return gx[x];
}
signed main()
{
	n=read();
	opt[0]=3;
	for(int i=1;i<=n;i++)
	{
		add(0,i);
		pos[i]=i;
		val[i]=read();
		opt[i]=1;
	}
	m=read();
	for(int i=n+1;i<=n+m;i++)
	{
		opt[i]=read();
		if(opt[i]==1)
			pos[i]=read(),val[i]=read();
		else if(opt[i]==2)
			val[i]=read();
		else
		{
			int c=read();
			while(c--)
			{
				int x=read();
				add(i,x+n);
			}
		}
	}
	q=read();
	for(int i=1;i<=q;i++)
	{
		int x=read();
		add(0,x+n);
	}
	memset(gx,-1,sizeof(gx));
	dfs(0);
	dp[0]=1;
	for(int i=cnt;i>=1;i--)
	{
		int x=id[i];
		for(int j=head[x];j;j=nxt[j])
		{
			int y=to[j];
			dp[y]=(dp[y]+dp[x]*1ll*lazy[j])%mod;
		}
		if(opt[x]==1)
			ans[pos[x]]=(ans[pos[x]]+dp[x]*1ll*val[x])%mod;
	}
	for(int i=1;i<=n;i++)
		printf("%lld ",ans[i]);
	puts("");
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM