【AtCoder Grand Contest 020 F】Arcs on a Circle

状压 DP,暴力。

题目大意:

给定一个长度为 $C$ 的圆,和 $n$ 条长度为整数的弧。现在把每条弧等概率放到圆上,问这些弧恰好覆盖整个圆的概率为多少。

题解:

首先我们可以钦定最长的弧的位置,并将其开始的位置记为 $0$,然后变成线段覆盖的问题。注意若不取最长的,则可能有一条弧覆盖了区间的一个后缀加一个前缀。而取最长的则避免了这个问题。

考虑两条弧的起点为 $p_i,p_j$,设 $p_i\lt p_j$。令 $[p_i]$ 表示取 $p_i$ 的整数部分,$\{p_i\}$ 表示取 $p_i$ 的实数部分。则相交有两种情况:

  • $[p_i]+l_i\gt [p_j]$。

  • $[p_i]+l_i=[p_j]$ 且 $\{p_i\}\gt \{p_j\}$。

由于实数部分均匀随机分布,所以可以不考虑相等的情况。容易发现,两条弧是否相交,只和它们整数部分的值和实数部分的相对大小有关。

那么可以将整个圆离散成 $nC$ 个位置。

考虑 $O((n-1)!)$ 枚举它们的相对大小并计算方案数。

使用状压 DP,令 $f[S][r]$ 表示用了集合 $S$ 中的弧,覆盖到了位置 $r$ 的方案数($r$ 左边已经被覆盖)。

按照 $r$ 从小到大转移,每次转移所用的弧是固定的,分使用还是不使用即可。

用合法方案数除以总方案数(每条弧的整数部分有 $C$ 种取值,所以总数为 $C^{n-1}$)。

最后除以排列个数即可。

时间复杂度 $O((n-1)!2^{n-1}n^2C^2)$。

Code:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<functional>
#include<cstring>
using namespace std;
const int N=6,nC=324;
int n,C,L[N];
int f[1<<N-1][nC];
unsigned long long ans;
int main(){
    scanf("%d%d",&n,&C);
    for(int i=0;i<n;++i)scanf("%d",L+i);
    sort(L,L+n);
    int tot=0;
    do{
        ++tot;
        memset(f,0,sizeof f);
        f[0][n*L[n-1]]=1; 
        for(int i=1;i<=n*C;++i)if(i%n){
            const int id=i%n-1,t=min(i+L[id]*n,n*C);
            for(int S=0;S<1<<n-1;++S)
            if(!(S>>id&1))
            for(int k=i;k<=n*C;++k)
            f[S|1<<id][max(k,t)]+=f[S][k];
        }
        ans+=f[(1<<n-1)-1][n*C];
    }while(next_permutation(L,L+n-1));
    printf("%.16g\n",1.*ans/tot/pow(C,n-1));
    return 0;
}