完全没有思路,还以为要维护多项式积分然后容斥啥的。看了wxh010910的题解,果然还是自己太菜了,___txdy。
我们不妨先固定一个最长的段的起点作为原点,这样不会循环覆盖了。起点在实数范围内随机非常奇怪,但是我们只关心它们是否覆盖了整个环,于是对于每个圆弧的起点的小数部分,我们并不关心它具体是多少,而只关心它们之间的相对大小关系。
所以我们可以
O
(
(
n
−
1
)
!
)
\mathcal O((n-1)!)
O((n−1)!)枚举小数部分间的相对大小关系,发现这时候我们可以理解为起点是长度为
O
(
n
C
)
\mathcal O(nC)
O(nC)的环上的整点,就可以DP了,状态
F
[
i
]
[
j
]
[
S
]
F[i][j][S]
F[i][j][S]表示当前考虑到第
i
i
i个整点,最远覆盖到
j
≥
i
j\geq i
j≥i,已经用了
S
S
S这个集合中的段的方案数,转移直接考虑是否在当前位置放一个段即可。
时间复杂度
O
(
(
n
−
1
)
!
2
n
n
C
)
\mathcal O((n-1)!2^nnC)
O((n−1)!2nnC)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[1<<5][305];
int num[7],p[7];
ll dp(int n,int m) {
memset(f,0,sizeof(f));
f[0][num[n]*n]=1;
int r=m*n;
for(int i=0;i<m;i++)
for(int j=1;j<n;j++) {
int x=i*n+j,u=p[j]-1,nr=min(r,num[p[j]]*n+x);
for(int k=0;k<(1<<(n-1));k++)
if (!((k>>u)&1)) {
for(int l=x;l<=r;l++)
if (f[k][l]) f[k^(1<<u)][max(nr,l)]+=f[k][l];
}
}
return f[(1<<(n-1))-1][r];
}
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&num[i]);
sort(num+1,num+n+1);
for(int i=1;i<n;i++) p[i]=i;
ll s=0;
do {
s+=dp(n,m);
} while (next_permutation(p+1,p+n));
ll v=1;
for(int i=1;i<n;i++) v*=i*m;
printf("%.15f\n",(double)s/v);
return 0;
}