HDU5322 Hope(DP + CDQ分治 + NTT)
题目
Source
http://acm.hdu.edu.cn/showproblem.php?pid=5322
Description
Hope is a good thing, which can help you conquer obstacles in your life, just keep fighting, and solve the problem below.
In mathematics, the notion of permutation relates to the act of arranging all the members of a set into some sequence or order, or if the set is already ordered, rearranging (reordering) its elements, a process called permuting. These differ from combinations, which are selections of some members of a set where order is disregarded. For example, written as tuples, there are six permutations of the set {1,2,3}, namely: (1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), and (3,2,1). These are all the possible orderings of this three element set. As another example, an anagram of a word, all of whose letters are different, is a permutation of its letters. In this example, the letters are already ordered in the original word and the anagram is a reordering of the letters.
There is a permutation A1,A2,...An, now we define its value as below:
For each Ai, if there exists a minimum j satisfies j>i and Aj>Ai , then connect an edge between Ai and Aj , so after we connect all the edges, there is a graph G, calculate the product of the number of nodes in each component as an integer P. The permutation value is P * P.Now, Mr. Zstu wants to know the sum of all the permutation value of n. In case the answer is very big, please output the answer mod 998244353.
Just in case some of you can’t understand, all the permutations of 3 are
1 2 3
1 3 2
2 3 1
2 1 3
3 1 2
3 2 1
Input
There are multiple test cases.
There are no more than 10000 test cases.
Each test case is an integer n(1≤n≤100000).
Output
For each test case, output the answer as described above.
Sample Input
1
2
Sample Output
1
5
分析
题目大概说,对于1到n这n个数的任何一个排列A可以这样计算其价值:对所有下标i找到最小的j满足j>i且A[j]>A[i],然后i和j之间连边,最后所有连通块个数之积的平方就是该排列的价值。问所有排列的价值和是多少。
- 首先要得出这么一个结论:从下标1...x,如果下标x的数是最大的话,那么1...x-1就与x一起组成一个连通块了。
- 然后,$dp[i]表示i个互不相同的数的所有排列的价值和$
- 通过枚举最大数的位置j来转移:$dp[i]\ =\ \sum A_{i-1}^{j-1}j^2dp[i-j]$
- 可以整理成卷积形式:$dp[i]\ =\ (i-1)! \times \sum (j^2\times ((i-j)!)^{-1}dp[i-j])$
- 然后于是就能用FFT计算了,特别的是结果模998244353,直接用NTT即可;还有要利用CDQ分治加速,累加左半边已经求得的dp值对右半边的影响。时间复杂度$O(nlog^2n)$。
代码
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXN 262144 const int P=998244353; // 119 * 2 ^ 23 + 1 const int G=3; long long mul(long long x,long long y){ return (x*y-(long long)(x/(long double)P*y+1e-3)*P+P)%P; } long long qpow(long long x,long long k,long long p){ long long ret=1; while(k){ if(k&1) ret=mul(ret,x); k>>=1; x=mul(x,x); } return ret; } long long wn[25]; void getwn(){ for(int i=1; i<=21; ++i){ int t=1<<i; wn[i]=qpow(G,(P-1)/t,P); } } int len; void NTT(long long y[],int op){ for(int i=1,j=len>>1,k; i<len-1; ++i){ if(i<j) swap(y[i],y[j]); k=len>>1; while(j>=k){ j-=k; k>>=1; } if(j<k) j+=k; } int id=0; for(int h=2; h<=len; h<<=1) { ++id; for(int i=0; i<len; i+=h){ long long w=1; for(int j=i; j<i+(h>>1); ++j){ long long u=y[j],t=mul(y[j+h/2],w); y[j]=u+t; if(y[j]>=P) y[j]-=P; y[j+h/2]=u-t+P; if(y[j+h/2]>=P) y[j+h/2]-=P; w=mul(w,wn[id]); } } } if(op==-1){ for(int i=1; i<len/2; ++i) swap(y[i],y[len-i]); long long inv=qpow(len,P-2,P); for(int i=0; i<len; ++i) y[i]=mul(y[i],inv); } } void Convolution(long long A[],long long B[],int n){ for(len=1; len<(n<<1); len<<=1); for(int i=n; i<len; ++i){ A[i]=B[i]=0; } NTT(A,1); NTT(B,1); for(int i=0; i<len; ++i){ A[i]=mul(A[i],B[i]); } NTT(A,-1); } long long fact[MAXN]={1},fact_ine[MAXN]={1}; long long A[MAXN],B[MAXN]; long long d[MAXN]={1}; /* d[i] = fact[i-1] * Σj*j * fact_ine[i-j]*d[i-j] */ void cdq(int l,int r){ if(l==r) return; int mid=l+r>>1; cdq(l,mid); for(int i=l; i<=mid; ++i) A[i-l]=mul(fact_ine[i],d[i]); for(int i=1; i<=r-l; ++i) B[i]=mul(i,i); for(int i=mid-l+1; i<=r-l; ++i) A[i]=0; Convolution(A,B,r-l+1); for(int i=mid+1; i<=r; ++i){ d[i]+=mul(A[i-l],fact[i-1]); d[i]%=P; } cdq(mid+1,r); } int main(){ for(int i=1; i<=100000; ++i){ fact[i]=mul(fact[i-1],i); fact_ine[i]=qpow(fact[i],P-2,P); } getwn(); cdq(0,100000); int n; while(~scanf("%d",&n)){ printf("%I64d\n",d[n]); } return 0; }