AT3860-[AGC020F]Arcs on a Circle【dp】
正题
题目链接:https://www.luogu.com.cn/problem/AT3860
题目大意
有一个周长为\(m\)的圆,\(n\)条线段,第\(i\)条长度为\(a_i\),将线段贴在圆的随机位置上,求整个圆都被覆盖的概率。
\(1\leq n\leq 6,1\leq m\le 50\)
解题思路
这种随机实数的问题我们可以从排名方面考虑。
先固定最长的一条线不动且左端点作为起点就可以不需要考虑环的问题了,然后对于每条线的开头离上一个整点的距离\(d\)进行一个排序,就可以作为每条线的排名了。
具体地,我们枚举一个排列作为排名,那这样就变为了\(n\times m\)个点(每个排名),考虑用\(dp\)解决问题。
首先我们需要记录线段的使用状态,然后我们一个一个位置考虑线段是否填,然后还需要目前延伸到的最末尾位置。
设\(f_{l,r,s}\)表示目前填到\(l\),最远延伸到\(r\),线段使用状态为\(s\)时的概率,然后因为我们线段开头的排名是确定的,所以对于一个\(l\)只能有一条线段可以填,可以省去枚举这条线段的时间。
时间复杂度:\(O(n^2m^22^nn!)\)
实际上常数很小,可以通过本题
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const double eps=1e-9;
int n,m,cnt,a[10];
double ans,f[500][70];
int main()
{
freopen("circle.in","r",stdin);
freopen("circle.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d",&a[i]),a[i]=a[i]*n;
sort(a,a+n);
do{
int MS=(1<<n-1);
for(int i=0;i<=n*m;i++)
for(int s=0;s<MS;s++)
f[i][s]=0;
f[a[n-1]][0]=1;
for(int i=1;i<=n*m;i++){
if(i%n==0)continue;int x=i%n-1;
for(int j=i;j<=n*m;j++){
for(int s=0;s<MS;s++){
if(f[j][s]<eps||((s>>x)&1))continue;
f[min(n*m,max(j,i+a[x]))][s|(1<<x)]+=f[j][s];
}
}
}
ans+=f[n*m][MS-1];cnt++;
}while(next_permutation(a,a+n-1));
printf("%.12lf",ans/(double)cnt/pow(m,n-1));
return 0;
}