---
# System prepended metadata

title: 第三屆科揚盃資訊科 editorial

---

# 第三屆科揚盃資訊科 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;
}
```
:::