HDU5322 Hope

題目鏈接

題目大意

對於一個11 ~nn 的排列(記排列的第ii 個數爲P_iPi ),定義這個排列的價值如下:
對於Pi ,若存在最小的j>i 使得Pj>Pi ,則將ij 合併到一個集合內.設每個集合的元素個數的乘積爲p ,那麼p2 即爲這個排列的價值.
現給定n ,求出所有1 ~n 的排列的價值和,對大素數mod=998244353 取模.
1n105 ,數據組數cas104 .

做題流程

虛比賽的時候題目看都沒看…賽後看了題解說要分治+FFT,於是滾去學了FFT.
然後學完了卻發現這題根本不用FFT…
所以說是被這道題的陣勢給嚇到了..
千萬不能被算法駕馭了….

題解

考慮往1 ~n1 的排列中加入n ,這時它前面的數都會與n 併到一個集合內,而它後面的數與前面的數會斷絕聯繫.
嘗試令dp[i] 表示1 ~i 的所有排列的價值之和,預處理出所有的dp值然後O(1) 回答詢問.
枚舉將i 插入到排列的第j 個位置(1ji) ,那麼前面j 個數的價值爲j2 ,而因爲排列的價值只與其中元素的相對大小關係有關,所以後面的數的價值與1 ~ij 的排列的價值一一對應.
前面j1 個數的方案數爲Aj1i1 ,對於每種方案後面都可以任意放11 ~i-jij 的排列.
於是得到略神的dp轉移方程:

dp[i]=j=1iAj1i1j2dp[ij].

注意有i=j 的情況,所以特別規定dp[0]=1 .
樸素轉移的複雜度是O(n2) .
然而把式子化一下就會發現可以前綴和優化了.

dp[i]=j=1i(j1)!(ij)!j2dp[ij]=(i1)!j=1ij2dp[ij]inv((ij)!).

k=ij[0,i) .則有:

dp[i]=(i1)!k=0i1(ik)2dp[k]inv(k!).

再設t=dp[k]inv(k!) .則:

dp[i]=(i1)!k=0i1i2t2ikt+k2t.

可以看出來只要維護關於t 的三個前綴和轉移就變成O(1) 了.
但是預處理因爲要求逆元複雜度是O(nlgmod) 的.


補:後來知道了可以O(n) 預處理逆元…

代碼

#include<cstdio>
const int N=1e5,mod=998244353;
int dp[N+5],fact[N+5],inv[N+5];
int fast_mod_pow(int a,int b){
    int res=1;
    for(;b;b>>=1,a=1ll*a*a%mod)
        if(b&1)res=1ll*res*a%mod;
    return res;
}
void init(){
    fact[0]=inv[0]=1;
    for(int i=1;i<=N;++i){
        fact[i]=1ll*fact[i-1]*i%mod;
        inv[i]=fast_mod_pow(fact[i],mod-2);
    }
    int sum1=0,sum2=0,sum3=0;
    for(int i=0;i<=N;++i){
        if(!i)dp[i]=1;
        else dp[i]=1ll*fact[i-1]*((1ll*i*i%mod*sum1%mod-2ll*i*sum2%mod+sum3)%mod+mod)%mod;
        sum1=(sum1+1ll*dp[i]*inv[i])%mod;
        sum2=(sum2+1ll*i*dp[i]%mod*inv[i])%mod;
        sum3=(sum3+1ll*i*i%mod*dp[i]%mod*inv[i])%mod;
    }
}
int main(){
    init();
    int n;
    while(~scanf("%d",&n))
        printf("%d\n",dp[n]);
    return 0;
}
/*

    Jun.18.16

    Tags:math
    Submissions:1

    Exe.Time 62MS
    Exe.Memory 2588K
    Code Len. 726B

*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章