CSP-S2020 T3 函數調用
題目描述
函數是各種編程語言中一項重要的概念,借助函數,我們總可以將復雜的任務分解成一個個相對簡單的子任務,直到細化為十分簡單的基礎操作,從而使代碼的組織更加嚴密、更加有條理。然而,過多的函數調用也會導致額外的開銷,影響程序的運行效率。
某數據庫應用程序提供了若干函數用以維護數據。已知這些函數的功能可分為三類:
- 將數據中的指定元素加上一個值;
- 將數據中的每一個元素乘以一個相同值;
- 依次執行若干次函數調用,保證不會出現遞歸(即不會直接或間接地調用本身)。
在使用該數據庫應用時,用戶可一次性輸入要調用的函數序列(一個函數可能被調用多次),在依次執行完序列中的函數后,系統中的數據被加以更新。某一天,小 A 在應用該數據庫程序處理數據時遇到了困難:由於頻繁而低效的函數調用,系統在執行操作時進入了無響應的狀態,他只好強制結束了數據庫程序。為了計算出正確數據,小 A 查閱了軟件的文檔,了解到每個函數的具體功能信息,現在他想請你根據這些信息幫他計算出更新后的數據應該是多少。
輸入格式
第一行一個正整數 nn,表示數據的個數。
第二行 nn 個整數,第 ii 個整數表示下標為 ii 的數據的初始值為 a_ia**i。
第三行一個正整數 mm,表示數據庫應用程序提供的函數個數。函數從 1 \sim m1∼m 編號。
接下來 mm 行中,第 jj(1 \le j \le m1≤j≤m)行的第一個整數為 T_jT**j,表示 jj 號函數的類型:
- 若 T_j = 1T**j=1,接下來兩個整數 P_j, V_jP**j,V**j 分別表示要執行加法的元素的下標及其增加的值;
- 若 T_j = 2T**j=2,接下來一個整數 V_jV**j 表示所有元素所乘的值;
- 若 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;
}
