题目大意:
有一个排列,长度为n,一个区间为连续段当且仅当最大值减最小值加一为区间的长度。问所有长度为n的排列,有多少个满足只有长度为1或n的连续段。要求n^3求出所有长度为1到n的答案。
思考:
考虑连续段的性质。容易发现的是,两个相交连续段的并还是一个连续段。
首先我们特判n=2。然后我们i使用容斥,考虑所有不应该被算到答案里的排列,那么可以通过以下算法把它们划分为两类:
1.从当前左端点找到最右的且不能为n(注意这个下标是原序列的下标)的右端点R,使得[左端点,R]为连续段。
2.从当前右端点找到最左的且不能为1的左端点L,使得[L,右端点]为连续段。
3.计数器cnt加一。
4.若两区间的并覆盖了未覆盖的区间,结束过程;否则将区间[1,R],[L,n]覆盖,左端点变为R+1,右端点变为L-1,重复上述过程。
我们首先知道,这个算法一定会结束。于是我们根据cnt分类:若cnt=1,则将该排列分到第1类,否则分到第2类。可以发现,这样是良定义的,即第一类中划分出的区间一定是极大的,并且它们的并为整个排列。第2类中划分出的区间一定是极大且互不相交的:因为一旦出现了一个更大的能包含它们的区间,它在上述算法中一定会被选到(根据相交连续段的并还是连续段的性质)。
因此,我们考虑计算这两类中的贡献。
对于第1类,我们首先知道要么是左边的连续段,要么是右边的连续段,它包含数字1。我们假设1在左边,并枚举左边的连续段的右端点i,那么答案会减去$2*\sum_{i=1}^{n-1}{I(n-i)*i!}$,其中$I(n)$表示所有1~n-1的前缀均不构成包含1的连续段的排列个数(注意I(n-i),这个写法有助于理解)。正确性在于我们钦定右边n-i个位置不管怎么选都无法构成长度大于i的且包含1的连续段,而左边的数字为1~i,因此[1,i]这个区间一定是连续段,从而左边的连续段第一次出现的右端点一定为i。而显然右边的区间的左端点小于等于i。这样算出来的一定是所有的第1类贡献。
$I(n)$的计算:$I(n)=n!-\sum_{i=1}^{n-1}{I(i)*(n-i)!}$,理由是容斥,枚举小于n且第一次出现包含1的连续段[1,i],剩下显然随便填。
对于第2类,它的结构与我们需要计算的答案高度地类似。我们知道,第2类中的所有方案的区间数一定大于等于3,考虑一个方案的区间划分[l1,r1],[l2,r2],...,[lk,rk],如果我们知道了它们的偏序关系(即一个区间要么最大值小于另一个区间的最小值,要么最小值大于另一个区间的最大值),那我们就能将数字1~n唯一地划分到这些区间中。而这样本质不同的偏序关系恰好有$ans(k)$个!其中$ans(n)$是我们最后要求的答案。于是我们只需要计算$\prod{(r_i-l_i+1)!}$的和即可。这部分是三次方的。
于是,我们就有答案$ans(n)=n!-2*\sum_{i=1}^{n-1}{I(i)*(n-i)!}-\sum_{i=3}^{n-1}{B(n,i)*ans_i}$,其中$B(n,i)$为上面那个乘积的和。注意后面的减法下界是3,上界是n-1(因为至少要有一段长度大于等于2)。
最后记得特判n=2,因为它并不在上述讨论中。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long int ll; 4 const int lim=400; 5 int n; 6 ll f[405],g[405][405],ans[405],fac[405]; 7 ll mod; 8 inline void add(ll&x,ll y) 9 { 10 x=(x+y)%mod; 11 } 12 int main() 13 { 14 ios::sync_with_stdio(false); 15 int T; 16 cin>>T>>mod; 17 fac[0]=1; 18 for(int i=1;i<=lim;++i) 19 fac[i]=fac[i-1]*i%mod; 20 for(int i=1;i<=lim;++i) 21 { 22 f[i]=fac[i]; 23 for(int j=1;j<=i-1;++j) 24 add(f[i],-f[j]*fac[i-j]); 25 } 26 g[0][0]=1; 27 for(int i=1;i<=lim;++i) 28 for(int j=1;j<=i;++j) 29 for(int t=1;t<=i;++t) 30 add(g[i][j],g[i-t][j-1]*fac[t]); 31 for(int i=1;i<=lim;++i) 32 { 33 ans[i]=fac[i]; 34 for(int j=1;j<=i-1;++j) 35 add(ans[i],-f[j]*fac[i-j]*2); 36 for(int j=3;j<=i-1;++j) 37 add(ans[i],-g[i][j]*ans[j]); 38 } 39 ans[2]=2;// !!!!!!! 40 while(T--) 41 { 42 int x; 43 cin>>x; 44 cout<<(ans[x]%mod+mod)%mod<<endl; 45 } 46 return 0; 47 }