The 2020 ICPC Asia Macau Regional Contest A - Accelerator
題目連結:https://codeforces.com/gym/103119/problem/A
推薦部落格:https://fanfansann.blog.csdn.net/article/details/118528285
題意:(複製上面推薦部落格的)
思路:
上述式子化簡,可以得到a1*a2*...*an + a2*a3*...*an + ... + an
求該式子所有排列的期望,就相當於:所有排列的貢獻之和 / 排列數
全排列個數顯然是 n!
關鍵在於怎麼計算所有排列之和,先畫畫圖,假設陣列是【1,2,3】
那麼答案就是(15+14+12+10+10+9) / 6 = 70 / 6
按列來考慮,假設 f(x)是 “ 長度為x的所有排列的貢獻和 ”
問題就轉化成如何快速求 f(x)
假設求f(2),那麼所有排列就是{【1,2】,【1,3】,【2,3】,【2,1】,【3,1】,【3,2】}
可見,就是在3個數中任選2個出來,就可以組成一個序列,而且每個序列的數字可以交換位置。
這像什麼呢?在一堆數中隨意挑幾個數出來合在一起求貢獻,應該要想到多項式相乘
對於單個ai,我們先構造出他的多項式,
關於冪次,ai只有取和不取,所以冪次設成0和1,冪次為0代表不取這個數,冪次為1代表取這個數,
關於係數,當不取ai的時候,無貢獻,相當於乘1,當取ai的時候,貢獻為ai,所以冪次為0的係數為1,冪次為1的係數為ai
單個ai的多項式如圖
我們把所有ai的多項式乘起來,那麼結果多項式中,冪次為x的係數就是長度為x的排列的貢獻和(注意:這裡的【1,2】和【2,1】是一種排列)
舉個例子,假設只有a1和a2
多項式相乘:(1 * X0 + a1 * x1) * (1 * X0 + a2 * x1) == 1 * X0 + (a1+ a2)* x1 + a1 * a2 * X2
冪次為2的係數是a1* a2, 並沒有出現a2* a1
這和我們的f(x)還有點差距,少了一些排列的貢獻和,但是注意到沒有算進去的排列,都可以通過改變順序轉換成某個算出來的排列,比如a2
可以通過算期望來計算出f(x),假設g(x)為冪次為x的係數,期望 = 權重* 概率 ,權重為g(x),概率為 n! / C(n,x) ,化簡一下概率就是 x! * (n-x)!
說明一下這個概率是怎麼算的,n!表示一共會出現的排列數(可以看上面的表格,不管x是多少,排列數都是一樣的),C(n,x) 表示n個數裡面選x個組成的排列數,即g(x)是由多少項相加得到
g(x)乘這個概率就相當於把沒算的部分加上去,得到f(x)
至此,我們可以算出所有的f(x),那麼答案就是(f(1) + f(2) + ... + f(n)) / n!
算g(x)可以用NTT,n個多項式相乘,可以看成線段樹的操作,每個節點看成一個多項式,線段樹每個最底層的節點相當於一個多項式,往上合併,兩個節點合併的時候使用NTT合併,一直合到根節點就可以算出n個多項式相乘的結果
看了很多部落格,這種操作應該叫分治NTT,不過我喜歡看成線段樹來理解。總的時間複雜度是O(n * logn * logn)
注意:編譯器選這個有優化的,不然大概率TLE
程式碼:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6 + 7;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll n;
const ll p=998244353,G=3,Gi=332748118,mod = 998244353;
ll limit=1,L=0,r[maxn];
ll a[maxn],jie[maxn],q[maxn],f1[maxn],f2[maxn];
ll qpow(ll a,ll n) {
ll c=1;
while(n) {
if(n&1) c = c * a % mod;
n >>= 1;
a = a * a % mod;
}
return c;
}
ll C(ll n,ll m) {
return jie[n] * q[m] % p * q[n-m] % p;
}
void init(ll len) {
L = 0;
limit = 1;
while(limit<=len) limit<<=1,L++;
for (int i=0; i<limit; ++i)
r[i] = (r[i>>1]>>1) | ((i&1)<<(L-1));
}
void NTT(ll *A,int type) {
for (int i=0; i<limit; ++i)
if(i<r[i]) swap(A[i],A[r[i]]);
for (int mid=1; mid<limit; mid<<=1) {
ll Wn = qpow(type==1?G:Gi,(p-1)/(mid<<1));
for (int j=0; j<limit; j+=(mid<<1)) {
ll w=1;
for (int k=0; k<mid; k++,w=(w*Wn)%p) {
ll x=A[j+k],y=w*A[j+k+mid]%p;
A[j+k] = (x+y)%p;
A[j+k+mid]=(x-y+p)%p;
}
}
}
}
void gao(int l,int r,vector<ll> &f) {
if(l == r) {
f[0] = 1;
f[1] = a[l];
return;
}
int mid = l + r >> 1;
int len1 = mid - l + 1, len2 = r - mid;
vector<ll>w1(len1+7,0),w2(len2+7,0);
gao(l,mid,w1);
gao(mid+1,r,w2);
for (int i=0; i<=len1; ++i) f1[i] = w1[i];
for (int i=0; i<=len2; ++i) f2[i] = w2[i];
int len = len1 + len2;
init(len);
for (int i=len1+1; i<limit; ++i) f1[i] = 0;
for (int i=len2+1; i<limit; ++i) f2[i] = 0;
NTT(f1,1);
NTT(f2,1);
for (int i=0; i<=limit; ++i) f1[i] = (f1[i] * f2[i]) % p;
NTT(f1,-1);
ll inv = qpow(limit,p-2);
for (int i=0; i<=len; ++i) f[i] = f1[i] * inv % p;
}
void solve() {
scanf("%lld",&n);
for (int i=1; i<=n; ++i) scanf("%lld",&a[i]);
vector<ll>b(n+7,0);
gao(1,n,b);
ll ans = 0;
for (int i=1; i<=n; ++i) {
ans = (ans + b[i] * jie[i] % p * jie[n-i] % p + p) % p;
// printf("%d %lld %lld\n",i,jie[n] % p * qpow(C(n,i),p-2) % p,b[i]);
}
// printf("%lld\n",ans);
printf("%lld\n",ans * q[n] % p);
}
int main() {
jie[0] = 1;
for (int i=1; i<=100000; ++i) jie[i] = jie[i-1] * i % p;
q[100000] = qpow(jie[100000],p-2);
for (int i=100000-1; i>=0; --i) q[i] = q[i+1] * (i+1) % p;
int t;
scanf("%d",&t);
while(t--) {
solve();
}
return 0;
}