HDU 5322 Hope (分治 + NTT)

題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=5322

題目大意:對於1~n的全排列中的任意一個排列,對於在排列中的任意一個 i,如果存在一個離 i 最近的 j 滿足 i < j 且 A[i] < A[j],就在 i 和 j 之間建一條無向邊。照此規矩建完邊之後,如果聯通圖內的點數爲P,那麼這一個聯通圖的貢獻就是P*P,種排列的貢獻就爲所有聯通圖的貢獻的乘積。現在給你一個n,要你求出n的所有排列的貢獻之和。

題目思路:由於這個題目的組數特別多,所以我們考慮用dp預處理來解決這個問題。

dp[i] 表示 長度爲 i 的排列的貢獻之和。

我們考慮對於長度爲 i 的序列中的某一個序列當 最大的數 i 位於第 j 位時,前 j - 1 個數必然會與 i 形成聯通塊,後面的 i - j 個數就不會與前面的點聯通。這樣我們就可以推出如下的狀態轉移方程:

dp[i] = \sum_{j = 1}^{i}C(i-1,j-1)*(j-1)!*j^2*dp[i-j]

表示從除了最大值 i 以外的 i - 1 個數中選出 j - 1 一個放到 前 j - 1個位置裏,同時這個j - 1個數總共有 (j - 1) ! 種排列方式,這個聯通塊對答案的貢獻就爲 j^2,再乘上後面 i - j 個數自己形成聯通塊的貢獻之和就是最終答案了。

我們接着再用組合數的性質化簡一下這個式子

C(i-1,j-1)=\frac{(i-1)!}{(j-1)!*(i-j)!}

dp[i] = (i-1)!*\sum_{j = 1}^{i}(i-j)!*dp[i-j]*j^2

接着令A[i-j] = (i-j)!*dp[i-j]B[j]=j^2

式子就化成了dp[i]=(i-1)!*\sum_{j+k=i}^{ }A[j]*B[k]

這個式子就能用FFT來求卷積了(本題由於有模數,所以可以選擇用NTT來降低精度的誤差)。

但由於是要預處理出所有的dp的值,所以我們可以選擇用分治來降低複雜度,這樣總的複雜度就爲O(n*logn*logn+T)。

具體實現看代碼:

#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define lowbit(x) x&-x
#define clr(a) memset(a,0,sizeof(a))
#define _INF(a) memset(a,0x3f,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
#define debug(x) cout<<"["<<x<<"]"<<endl
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int,int>pii;
const int MX = 1e5+7;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;

const int P = 998244353;
const int G = 3;
const int NUM = 20;

ll  wn[NUM];
ll  va[MX<<2],vb[MX<<2];
ll quick_mod(ll a, ll x, ll mod) {
    ll ans = 1;
    a %= mod;
    while(x) {
        if(x & 1)ans = ans * a % mod;
        x >>= 1;
        a = a * a % mod;
    }
    return ans;
}
//在程序的開頭就要放
void GetWn() {
    for(int i = 0; i < NUM; i++) {
        int t = 1 << i;
        wn[i] = quick_mod(G, (P - 1) / t, P);
    }
}
void Rader(ll F[], int len) {
    int j = len >> 1;
    for(int i = 1; i < len - 1; i++) {
        if(i < j) swap(F[i], F[j]);
        int k = len >> 1;
        while(j >= k)j -= k,k >>= 1;
        if(j < k) j += k;
    }
}
void NTT(ll F[], int len, int t) {
    Rader(F, len);
    int id = 0;
    for(int h = 2; h <= len; h <<= 1) {
        id++;
        for(int j = 0; j < len; j += h) {
            ll E = 1;
            for(int k = j; k < j + h / 2; k++) {
                ll u = F[k];
                ll v = E * F[k + h / 2] % P;
                F[k] = (u + v) % P;
                F[k + h / 2] = (u - v + P) % P;
                E = E * wn[id] % P;
            }
        }
    }
    if(t == -1) {
        for(int i = 1; i < len / 2; i++)swap(F[i], F[len - i]);
        ll inv = quick_mod(len, P - 2, P);
        for(int i = 0; i < len; i++)F[i] = F[i] * inv % P;
    }
}
void Conv(ll a[], ll b[], int len) {
    NTT(a, len, 1);
    NTT(b, len, 1);
    for(int i = 0; i < len; i++)a[i] = a[i] * b[i] % P;
    NTT(a, len, -1);
}
ll dp[MX],f[MX],invf[MX];
void init(){
	f[0] = f[1] = 1;
	for(int i = 2;i < MX;i++) f[i] = (f[i-1] * i) % P;
	invf[MX-1] = quick_mod(f[MX-1],P-2,P);
	for(int i = MX-2;i >= 0; i--) invf[i] = (invf[i+1] * (i+1)) % P;
}
void cdq(int l,int r){
	if(l == r) return;
	int m = (l + r) >> 1;
	cdq(l,m);
	int mx = (r - l + 1),len = 1;
	while(len <= mx) len <<= 1;
	for(int i = 0;i < len;i++){
		if(l + i <= m) va[i] = dp[l+i]*invf[l+i] % P;
		else va[i] = 0;
		if(l + i < r) vb[i] = (ll)(i+1)*(i+1) % P;
		else vb[i] = 0;
	}
	Conv(va,vb,len);
	for(int i = m + 1;i <= r;i++)
		dp[i] = (dp[i] + f[i-1]*(va[i-l-1]) % P) % P;
	cdq(m+1,r);
}
void pre_solve(){
	GetWn();
	init();
	dp[0] = 1;
	cdq(0,100000);
}

int main(){
	//FIN;
	pre_solve();int n;
	while(~scanf("%d",&n)) printf("%lld\n",dp[n]);
	return 0;
}

 

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