# 第三屆科揚盃資訊科 editorial link: https://hackmd.io/@BaoCoder613/rybj0r-_yx ## pA 純粹暴力求和,求和完判奇偶即可。注意到總和可能達到 $10^{11}$,要開 long long。 預期難度:800* :::spoiler code ```c++=1 #include <bits/stdc++.h> using namespace std; #define ll long long int main(){ int n; cin>>n; ll tot=0, a; for(int i=0; i<n; i++){ cin>>a; tot+=a; } if(tot%2) cout<<"yes"; else cout<<"no"; } ``` ::: ## pB 注意到 $f(x)^2=x^2$ 代表 $f(a)\in\{a, -a\}$ 對於任意 $a$ 都成立。因此我們先用一個陣列存每個數字出現幾次,再做位元枚舉。 預期難度:1100* :::spoiler code ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back int a[2000005]; vector <int> v; int main(){ int n, x; cin>>n; for(int i=0; i<n; i++){ cin>>x; x+=1000000; a[x]++; } for(int i=0; i<=1000000; i++){ if(a[1000000+i]-a[1000000-i]){ v.pb(i*(a[1000000+i]-a[1000000-i])); } } n=v.size(); int sum=0; bool solved=false; for(int i=0; i<(1<<n); i++){ sum=0; for(int j=0; j<n; j++){ if(i&(1<<j)) sum+=v[j]; else sum-=v[j]; } if(!sum){ solved=true; break; } } if(solved) cout<<"yes"; else cout<<"no"; } ``` ::: ## pC 這題有兩種做法。 做法一:建 DSU,先把沒壞的路連起來。接著把剩餘的邊按照邊權排序,依序檢查,如果兩端點還沒有連通的話,就花費邊權的 cost 把它們用 DSU 連起來。重複直到圖連通為止。(後面其實類似 Kruskal 的做法) 做法二:把沒壞的路邊權都改成$0$,直接找最小生成樹(Kruskal 或 Prim)。 預期難度:1300* :::spoiler code(做法一) ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back struct edge{ int u, v; ll w; }; bool MySort(edge e1, edge e2){ return e1.w<e2.w; } const int MAXN=100005; int dsu[MAXN], sz[MAXN]; vector <edge> E; int cc; void init(int n){ for(int i=1; i<=n; i++){ dsu[i]=i; sz[i]=1; } cc=n; } int fa(int a){ if(dsu[a]==a) return a; dsu[a]=fa(dsu[a]); return fa(dsu[a]); } int A, B; void join(int a, int b){ A=fa(a); B=fa(b); if(A==B) return; cc--; if(sz[A]>sz[B]){ dsu[B]=A; sz[A]+=sz[B]; } else{ dsu[A]=B; sz[B]+=sz[A]; } } ll ans=0; void kruskal(){ sort(E.begin(), E.end(), MySort); for(edge e: E){ if(cc==1) return; if(fa(e.u)==fa(e.v)) continue; ans+=e.w; join(e.u, e.v); } } int u[200005], v[200005]; ll w[200005]; int brk[200005]; int main(){ int n, m; cin>>n>>m; for(int i=1; i<=m; i++){ cin>>u[i]>>v[i]>>w[i]; } init(n); int a, k; cin>>k; for(int i=1; i<=k; i++){ cin>>a; brk[a]=1; E.pb({u[a], v[a], w[a]}); } for(int i=1; i<=m; i++){ if(!brk[i]) join(u[i], v[i]); } kruskal(); if(cc>1) cout<<-1; else cout<<ans; } ``` ::: 這題的 code 也滿長的,但也有 59 行的模板。 ## pD 計算幾何問題,需要處理線段和點、線段和線段關係。一個簡單的作法是去看該次詢問的線段和幾個點、幾個線段相交 (線段和線段交在頂點,也算相交) 交四個線段:一定有切到(一定交兩個頂點) 交三個線段:如果交兩個頂點,就沒切到。如果交一個頂點,就有切到 交兩個線段:如果交一個頂點,就沒切到。如果交零個頂點,就有切到 剩下情況:要嘛不可能,要嘛沒切到 預期難度:1500* :::spoiler code ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back struct pt{ ll x,y; bool operator < (pt b){ if(x==b.x) return y<b.y; return x<b.x; } bool operator > (pt b){ if(x==b.x) return y>b.y; return x>b.x; } bool operator == (pt b){ return ((x==b.x)&(y==b.y)); } pt operator + (pt b){ return {x+b.x, y+b.y}; } pt operator - (pt b){ return {x-b.x, y-b.y}; } pt operator * (ll c){ return {x*c, y*c}; } pt operator / (ll c){ return {x/c, y/c}; } ll operator * (pt b){ return x*b.x+y*b.y; } ll operator ^ (pt b){ return x*b.y-y*b.x; } }; int sign(ll x){ if(x==0) return x; if(x<0) return -1; return 1; } bool on (pt a, pt b, pt x){ ll abx=(a-x)^(b-x), dt=(a-x)*(b-x); return ((abx==0)&&(dt<=0)); } int dir(pt a, pt b, pt x){ ll abx=(a-x)^(b-x); return sign(abx); } bool banana(pt a, pt b, pt c, pt d){ if(on(a, b, c)||on(a, b, d)) return true; if(on(c, d, a)||on(c, d, b)) return true; if((dir(a, b, c)!=dir(a, b, d))&&(dir(c, d, a)!=dir(c, d, b))) return true; return false; } int main(){ pt a, b; ll x; pt sq[5]; int q; cin>>x>>q; sq[0]={x, x}; sq[1]={-x, x}; sq[2]={-x, -x}; sq[3]={x, -x}; sq[4]={x, x}; int cnte, cntv, ans=0; while(q--){ cin>>a.x>>a.y>>b.x>>b.y; cnte=cntv=0; for(int i=0; i<4; i++){ if(on(a, b, sq[i])) cntv++; } for(int i=0; i<4; i++){ if(banana(a, b, sq[i], sq[i+1])) cnte++; } if(cnte==4) ans++; else if(cnte==3){ if(cntv==1) ans++; } else if(cnte==2){ if(cntv==0) ans++; } } cout<<ans; } ``` ::: PS: 雖然這題的 code 有 93 行,但是其實前 61 行都是模板。 ## pE 這題大野狼要在一個特定的 cost 之內最大化得到的價值,所以是一種背包 DP。可是你會發現這題的小豬會逃跑,付了 cost 不一定拿得到。因此我們必須多加一個維度,考慮第 $i$ 棟房子有沒有被吹倒。 假設 $dp[i][j][0/1]$ 代表考慮前 $i$ 棟房子,目前總花費至多 $j$,第 $i$ 棟房子有/沒有被吹倒,我們會有這些轉移式。 $$dp[i][j][0]=\begin{cases}\max(dp[i-1][j][0], dp[i-1][j][1]), \ d[i-1]\leq j \\ dp[i-1][j][0],\ d[i-1]>j\end{cases}$$ 然後當 $d[i-1]\leq j$ 的時候我們有 $$dp[i][j+d[i]][1]=\begin{cases}\max(dp[i-1][j][0], dp[i-1][j][1])+m[i], \ d[i-1]\leq d[i] \\ \max(dp[i-1][j][1]+m[i], dp[i-1][j][0]),\ d[i-1]>d[i]\end{cases}$$ 當 $d[i-1]>j$ 的時候我們有 $$dp[i][j+d[i]][1]=\begin{cases}dp[i-1][j][0]+m[i], \ d[i-1]\leq d[i] \\ dp[i-1][j][0],\ d[i-1]>d[i]\end{cases}$$ 直接兩層迴圈暴力轉移即可 預期難度:1800* :::spoiler code ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back int dp[3005][10005][2], d[3005], m[3005]; int main(){ int n, D; cin>>n>>D; for(int i=1; i<=n; i++) cin>>d[i]; for(int i=1; i<=n; i++) cin>>m[i]; for(int i=1; i<=n; i++){ for(int j=D; j>=0; j--){ if(j>=d[i-1]) dp[i][j][0]=max(dp[i-1][j][0], dp[i-1][j][1]); else dp[i][j][0]=dp[i-1][j][0]; if(j+d[i]>D) continue; if(d[i]>d[i-1]){ if(j>=d[i-1]) dp[i][j+d[i]][1]=max(dp[i-1][j][1], dp[i-1][j][0])+m[i]; else dp[i][j+d[i]][1]=dp[i-1][j][0]+m[i]; } else{ if(j>=d[i-1]) dp[i][j+d[i]][1]=max(dp[i-1][j][1]+m[i], dp[i-1][j][0]); else dp[i][j+d[i]][1]=dp[i-1][j][0]; } } } int ans=0; for(int i=0; i<=D; i++){ ans=max(ans, dp[n][i][1]); ans=max(ans, dp[n][i][0]); } cout<<ans; } ``` ::: ## pF 我們用兩顆懶標線段樹,一顆維護竹、一顆維護藥水。分別需要支援以下操作 竹:區間加值、區間求和、區間求 min 藥水:區間改值、區間求 xor 這樣兩種操作就可以如下處理 第一種:把使用的藥水區間求 xor 算出來之後,把該區間藥水歸零。接著對竹的線段樹區間加值。 第二種:把竹的區間最小值算出來,區間減值(跟加值一樣)。 最後我們可以利用竹的區間加值把所有高度都加一。 預期難度:2000* :::spoiler code ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back #define lc id*2+1 #define rc id*2+2 const int MAXN=200005; ll med[MAXN]; ll seg[MAXN*4], mn[MAXN*4], sum[MAXN*4], qz[MAXN*4], qm[MAXN*4]; void pullmed(int id){ seg[id]=seg[lc]^seg[rc]; } void pullb(int id){ mn[id]=min(mn[lc], mn[rc]); sum[id]=sum[lc]+sum[rc]; } void pushmed(int id){ if(!qz[id]) return; seg[lc]=0; qz[lc]=1; seg[rc]=0; qz[rc]=1; qz[id]=0; } void pushb(int id, int l, int r){ if(!qm[id]) return; int m=(l+r)>>1; mn[lc]+=qm[id]; qm[lc]+=qm[id]; sum[lc]+=qm[id]*(m-l+1); mn[rc]+=qm[id]; qm[rc]+=qm[id]; sum[rc]+=qm[id]*(r-m); qm[id]=0; } void buildmed(int id, int l, int r){ if(l==r){ seg[id]=med[l]; return; } int m=(l+r)>>1; buildmed(lc, l, m); buildmed(rc, m+1, r); pullmed(id); } void xormed(int id, int l, int r, int L, int R){ if(l>=L&&r<=R){ seg[id]=0; qz[id]=1; return; } pushmed(id); int m=(l+r)>>1; if(L<=m) xormed(lc, l, m, L, R); if(R>m) xormed(rc, m+1, r, L, R); pullmed(id); } void addb(int id, int l, int r, int L, int R, ll val){ if(l>=L&&r<=R){ mn[id]+=val; sum[id]+=val*(r-l+1); qm[id]+=val; return; } pushb(id, l, r); int m=(l+r)>>1; if(L<=m) addb(lc, l, m, L, R, val); if(R>m) addb(rc, m+1, r, L, R, val); pullb(id); } ll querymed(int id, int l, int r, int L, int R){ if(l>=L&&r<=R) return seg[id]; pushmed(id); int m=(l+r)>>1; ll res=0; if(L<=m) res^=querymed(lc, l, m, L, R); if(R>m) res^=querymed(rc, m+1, r, L, R); return res; } ll queryb(int id, int l, int r, int L, int R, int type){ if(l>=L&&r<=R){ if(type==0) return mn[id]; else return sum[id]; } pushb(id, l, r); int m=(l+r)>>1; ll res; if(type==0) res=9e18; else res=0; if(L<=m){ if(type==0) res=min(res, queryb(lc, l, m, L, R, 0)); else res+=queryb(lc, l, m, L, R, 1); } if(R>m){ if(type==0) res=min(res, queryb(rc, m+1, r, L, R, 0)); else res+=queryb(rc, m+1, r, L, R, 1); } return res; } int main(){ ios::sync_with_stdio(false); cin.tie(0); int n, k, q; cin>>n>>k>>q; for(int i=1; i<=k; i++){ cin>>med[i]; } buildmed(0, 1, k); int type, l1, r1, l2, r2; ll temp, mins, ans; while(q--){ cin>>type; if(type==1){ cin>>l1>>r1>>l2>>r2; temp=querymed(0, 1, k, l2, r2); xormed(0, 1, k, l2, r2); addb(0, 1, n, l1, r1, temp); } else{ cin>>l1>>r1; ans=queryb(0, 1, n, l1, r1, 1); cout<<ans<<'\n'; mins=queryb(0, 1, n, l1, r1, 0); addb(0, 1, n, l1, r1, -mins); } addb(0, 1, n, 1, n, 1); } } ``` ::: 這題是那種不用想的題目,完全考驗寫線段樹的能力。 ## pG 先觀察到 $$\sum_{j=1}^nd(i, j)\times (a_i^2+a_j^2)=a_i^2\sum_{j=1}^nd(i, j)+2a_i\sum_{j=1}^nd(i, j)\times a_j+\sum_{j=1}^nd(i, j)\times a_j^2$$ 考慮換根 DP。先定義後面三項分別是$dsq[i], dsl[i], ds[i]$,並且定義 $i$ 的子樹大小,子樹點權和,子樹點權平方和分別是 $sz[i], as[i], sqs[i]$。則對於某個節點 $cur$ 的子結點 $nxt$ 有以下轉移式 $$ds[nxt]=ds[cur]+sz[1]-(2*sz[nxt])$$ $$dsl[nxt]=dsl[cur]+as[1]-(2*as[nxt])$$ $$dsq[nxt]=dsq[cur]+sqs[1]-(2*sqs[nxt])$$ 而節點 $i$ 最終的答案就是 $$a[i]^2\times ds[i]+2\times a[i]\times dsl[i]+dsq[i]$$ 預期難度:2100* :::spoiler ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back const int MAXN=200005; ll mod=998244353; vector <int> G[MAXN]; ll dep[MAXN], a[MAXN], sq[MAXN], sz[MAXN], as[MAXN], sqs[MAXN]; ll ds[MAXN], dsl[MAXN], dsq[MAXN]; void dfspre(int cur, int lst){ sz[cur]=1; as[cur]=a[cur]; sqs[cur]=sq[cur]; dep[cur]=dep[lst]+1; for(int nxt: G[cur]){ if(nxt==lst) continue; dfspre(nxt, cur); sz[cur]+=sz[nxt]; if(sz[cur]>=mod) sz[cur]-=mod; as[cur]+=as[nxt]; if(as[cur]>=mod) as[cur]-=mod; sqs[cur]+=sqs[nxt]; if(sqs[cur]>=mod) sqs[cur]-=mod; } } void dfsfin(int cur, int lst){ for(int nxt: G[cur]){ if(nxt==lst) continue; ds[nxt]=ds[cur]+sz[1]-(2*sz[nxt]); ds[nxt]%=mod; if(ds[nxt]<0) ds[nxt]+=mod; dsl[nxt]=dsl[cur]+as[1]-(2*as[nxt]); dsl[nxt]%=mod; if(dsl[nxt]<0) dsl[nxt]+=mod; dsq[nxt]=dsq[cur]+sqs[1]-(2*sqs[nxt]); dsq[nxt]%=mod; if(dsq[nxt]<0) dsq[nxt]+=mod; dfsfin(nxt, cur); } } int main(){ int n; cin>>n; int v; for(int i=2; i<=n; i++){ cin>>v; G[v].pb(i); G[i].pb(v); } for(int i=1; i<=n; i++){ cin>>a[i]; sq[i]=(a[i]*a[i])%mod; } dep[0]=-1; dfspre(1, 0); for(int i=1; i<=n; i++){ ds[1]+=dep[i]; ds[1]%=mod; dsl[1]+=dep[i]*a[i]; dsl[1]%=mod; dsq[1]+=dep[i]*sq[i]; dsq[1]%=mod; } dfsfin(1, 0); ll ans; for(int i=1; i<=n; i++){ ans=a[i]*ds[i]; ans%=mod; ans*=a[i]; ans%=mod; ans+=(2*a[i]*dsl[i]); ans%=mod; ans+=dsq[i]; ans%=mod; cout<<ans<<" "; } } ``` ::: ## pH 定義 $pf[i]$ 為 $s$ 到第 $i$ 個字元的前綴,而 $pf'[i]$ 為 $s'$ 到第 $i$ 個字元的前綴 (其中 $s'$ 代表把 $s$ 中沒開的店改成 $。 注意到如果大食客 $i$ 在城市 $j$ 會付他吃的前 $k$ 家店的錢,代表 $pf[i]$ 跟 $pf'[j]$ 的後綴長度至少為 $k$,而且 $pf[i]$ 長度為 $k$ 的後綴同時為其前綴。 因此我們將考慮 $s$ 的失配樹,其中 $i$ 的父節點為 $\pi[i]$。如果沒有關門的店,那其實大食客 $i$ 在城市 $j$ 付的錢就會是從 $LCA(i, j)$ 吃到 $1$ 的錢。 注意到如果今天 $j<k$,則第 $i$ 個人會付 $LCA(i, j)$ 吃到 $1$ 的錢,但如果 $j>k$,第 $i$ 個人只會付 $LCA(i, \pi[j])$ 吃到 $1$ 的錢。如果 $j=k$ 那沒有人會付半毛錢。這怎麼處理呢? 考慮對 $s+\#+s'$ 字串做 KMP,假設這個新字串的失配函數為 $\pi'$。那麼我們只要查詢 $LCA(i, \pi'[j+n])$吃到 $1$ 的錢就好了($+n$ 是因為 $s'$ 字串在後方)。我們可以建立花費的前綴和 $pre$ 來加速。於是,我們會需要快速的處理這個函數的查詢 $$f(i)=\sum_{j=1}^n pre[LCA(i, j)]$$ 我們考慮換根 DP。先預處理每個節點的子樹大小 $sz$。注意到如果 $v$ 是 $u$ 的子節點,那麼對於 $v$ 的子樹內的任意節點 $w$,$LCA(v, w)\neq LCA(u, w)$。其餘的 LCA 則不變。因此我們有以下轉移式。 $$f(v)=f(u)-(pre[u]\times sz[v])+(pre[v]\times sz[v])$$ 最後我們從 $i=n+1$ 到 $i=2n$,分別回答 $f(\pi'[i])$ 即可。(當然也可以像下面的 code 一樣分 case 輸出) 預期難度:2400* :::spoiler code ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back const int MAXN=1000005; char s[MAXN*2]; int pi[MAXN*2]; ll pre[MAXN], sz[MAXN], dp[MAXN], a[26]; vector <int> G[MAXN]; void dfspre(int cur, int lst){ sz[cur]=1; for(int nxt: G[cur]){ if(nxt==lst) continue; dfspre(nxt, cur); sz[cur]+=sz[nxt]; } } void dfs(int cur, int lst){ dp[cur]=dp[lst]; dp[cur]-=pre[lst]*sz[cur]; dp[cur]+=pre[cur]*sz[cur]; for(int nxt: G[cur]){ if(nxt==lst) continue; dfs(nxt, cur); } } int main(){ int n, k; cin>>n>>k; string ss; cin>>ss; for(int i=0; i<n; i++) s[i]=ss[i]; s[n]='#'; for(int i=n+1; i<=2*n; i++){ s[i]=ss[i-n-1]; } s[k+n]='$'; for(int i=0; i<26; i++) cin>>a[i]; int j=0; for(int i=1; i<=2*n; i++){ while(j>0 && s[i]!=s[j]) j=pi[j-1]; if(s[i]==s[j]) j++; pi[i]=j; } for(int i=n; i>=1; i--){ pi[i]=pi[i-1]; G[i].pb(pi[i]); G[pi[i]].pb(i); } for(int i=1; i<=n; i++){ pre[i]=pre[i-1]+a[s[i-1]-'a']; } dfspre(0, 0); dfs(0, 0); for(int i=n+1; i<=2*n; i++){ if(i<n+k) cout<<dp[i-n]; else cout<<dp[pi[i]]; cout<<" "; } } ``` ::: ## pI 我們先定義 order 和原根 :::info 對於一個正整數 $n$ 以及一個與 $n$ 互質的整數 $a$,定義$\text{ord}_n(a)$ 為最小的正整數 $k$ 使得 $a^k\equiv 1\pmod{p}$。如果 $\text{ord}_n(a)=\varphi(n)$ 則稱 $a$ 為 $n$ 的原根 ::: 我們默認大家都會原根的性質。如果不會的話,可以先相信以下性質,證明略。 :::danger 1. $\text{ord}_n(a)$ 整除 $\varphi(n)$。 2. 質數有原根。 3. 給定質數 $p$ 和原根 $g$,$\{g, g^2, g^3, \cdots, g^{p-1}\}$ 構成模 $p$ 下的既約剩餘系。 ::: 接著我們注意到由餘式定理有 $f(x)$ 被 $x(x-1)$ 整除。我們觀察以下性質 :::danger 性質:$x^x$ 在模 $p$ 下每 $p(p-1)$ 個一循環。 證明:取一個 $p$ 的原根 $g$,注意到 $g, g+p, g+2p, g+3p, \cdots , g+p(p-2)$ 在模 $p-1$ 下兩兩不同餘。因此在模 $p$ 下有 $\{g, g^2, g^3, \cdots, g^{p-1}\}=\{g^g, g^{g+p}, g^{g+2p}, \cdots, g^{g+p(p-2)}\}$。因此 $x^x$ 的循環節大小並且循環節大小絕對超過 $p(p-2)$。又循環節大小必定是 $p$ 的倍數。因此其大小至少為 $p(p-1)$。而我們有 $(x+p(p-1))^{x+p(p-1)}\equiv x^{x+p(p-1)}\equiv x^x\times x^{p(p-1)}\equiv x^x\pmod{p}$。因此得證。 ::: 因此若 $1$ 到 $p(p-1)$ 有 $A$ 個滿足條件的數,那答案就會是 $\frac{Af(p)}{p(p-1)}$。現在我們要計算這個 $A$ 的值。 :::danger 性質:$$A=\sum_{s=1}^{p-1}\gcd(s, p-1)$$ 證明:考慮一個不被 $p$ 整除的正整數 $a$,以及一個原根 $g$,我們知道存在唯一一個小於 $p$ 的正整數 $k$ 使得 $g^k\equiv a\pmod{p}$。因此 $\{a^a, a^{a+p}, a^{a+2p}, \cdots, a^{a+p(p-2)}\}=\{a, a^2, a^3, \cdots, a^{p-1}\}=\{g^k, g^{2k}, g^{3k}, \cdots, g^{(p-1)k}\}$ 而當 $p-1\mid tk$ 的時候 $g^{tk}\equiv 1\pmod{p}$。也就是說,當$(p-1)/\gcd(k, p-1)$ 整除 $t$ 的時候都會好,因此總共有 $\gcd(k, p-1)$ 個 $t$ 會好。對每個 $1\leq a\leq p-1$ 計算總和即可。 ::: 這樣我們就可以 $O(p+n)$ 算出答案了。可是 $p$ 到六兆會噴爛欸,怎麼辦。我們定義 $gs(x)=\sum_{s=1}^x\gcd(s, x)$ 並觀察以下性質 ::: danger 性質: $$gs(x)=x\sum_{d|x}\frac{\varphi(d)}{d}$$ 證明: 注意到滿足 $\gcd(s, x)=k$ 的數有 $\varphi(\frac{x}{k})$ 個,因此 $$gs(x)=\sum_{k|x}k\varphi(\frac{x}{k})=\sum_{d|x}\frac{\varphi(d)}{\frac{x}{d}}=x\sum_{d|x}\frac{\varphi(d)}{d}$$ ::: 那這樣可以做什麼呢?我們希望它有積性,結果它就真的有了。 ::: danger 性質:$gs(xy)=gs(x)gs(y)$ 證明: $$\begin{align*}gs(xy)&=xy\sum_{d|xy}\frac{\varphi(d)}{d}=xy\sum_{d|x, e|y}\frac{\varphi{(de)}}{de}\\&=\left(\sum_{d|x}\frac{\varphi(d)}{d}\right)\left(\sum_{e|y}\frac{\varphi(e)}{e}\right)=gs(x)gs(y)\end{align*}$$ ::: 並且注意到 $gs(q^{\alpha})=(\alpha+1)q^{\alpha}-\alpha q^{\alpha-1}$ ($q$ 是質數)。。因此我們可以把 $p-1$ 質因數分解,對每個質因數計算 $gs$ 之後相乘。就可以算出 $A$。這樣總複雜度為 $O(\sqrt{p}+n)$,可以過。 預期難度:2900* :::spoiler code ```c++= #include <bits/stdc++.h> using namespace std; #define ll long long #define pb push_back #define pii pair<ll, int> ll mod=998244353; const int MAXN=200005; ll pw[MAXN],a[MAXN]; vector <pii> fac; ll inv(ll x){ if(x<=1) return x; return mod-mod/x*inv(mod%x)%mod; } void factorization(ll x){ ll fs=2, mv=x; int temp; while(fs*fs<=mv){ temp=0; while(mv%fs==0){ mv/=fs; temp++; } if(temp>0) fac.pb({fs, temp}); fs++; } if(mv>1) fac.pb({mv, 1}); } ll gs(ll pr, ll alpha){ ll temp=1, ans=0, prt=pr%mod; for(int i=1; i<=alpha-1; i++){ temp*=prt; temp%=mod; } ans-=((alpha*temp)%mod); temp*=prt; temp%=mod; ans+=(((alpha+1)*temp)%mod); ans%=mod; return ans; } int main(){ int n; ll p; cin>>n>>p; for(int i=0; i<=n; i++) cin>>a[i]; ll pm=p%mod; pw[0]=1; for(int i=1; i<=n; i++){ pw[i]=pw[i-1]*pm; pw[i]%=mod; } ll fp=0; for(int i=1; i<=n; i++){ fp+=pw[i]*a[i]; fp%=mod; } if(fp<0) fp+=mod; fp*=inv((pm*(pm-1))%mod); fp%=mod; factorization(p-1); for(pii pi: fac){ fp*=gs(pi.first, pi.second); fp%=mod; } cout<<fp; } ``` :::