AGC020F Arcs on a Circle

发布时间 2023-07-20 18:34:56作者: Ender_32k

先考虑只能放整点的情况,不难想到考虑 dp。

对于环上的 dp,考虑断环成链,即钦定一条线段的左端点为起点。这里我们令长度最长的线段的左端点为环的起点。

这样做有一个好处:我们不用考虑一条线段把环末尾覆盖再覆盖开头的空的情况,而当我们钦定一个长度较小的线段作为起点时,在环的末尾放一个长度较长的线段有可能覆盖到环开头的空隙中,这样是合法的,但判不到。

然后就相当于固定了一个前缀被覆盖,剩下 \(n-1\) 条线段,由于 \(n\) 的范围较小,不难想到状压。同时我们考虑从前往后枚举线段起点,这样任意时刻覆盖的都是一段前缀。具体来讲,我们设 \(f_{i,S}\) 表示覆盖了 \([0,i]\) 这段极长前缀,用了 \(S\) 集合中线段的方案数(最长线段,即我们已经钦定位置的线段不算)。

转移时枚举线段起点 \(i\)、要填的线段 \(j\)、上一刻 \(S\) 集合的状态 \(T\) 以及上一刻覆盖到的前缀 \([0,w]\) 即可。由于我们钦定了最长线段为起点,中途我们一旦留下了空隙以后就无法弥补了,所以 \(w\ge i\)。我们用 \(w\)\(i+l_j\) 去更新现在的极长前缀,那么有:

\[f_{\min(c,\max(w,i+l_j)),T\cup\{ j\}}\gets f_{w,T} \]

然后这是只能放整点的情况,现在考虑任意实点的情况。

考虑刚才的 dp 时间复杂度 \(O(c^2n2^n)\),发现这个时间限制相当富余,我们考虑乱搞。

具体来讲,我们将圆和弧细分成 \(m\) 段,满足 \(c\mid m\)。我们再给 \(l_i\)\(c\) 乘上 \(\frac{m}{c}\),做刚才的 dp。显然,当 \(m\) 越大时,可以取的整点就越多。当 \(m\to +∞\) 时,我们可以认为圆环上所有实点都可以取到。

于是我们又有一个 \(O(m^2n2^n)\) 的 dp 了,其中 \(m\to +∞\)。考虑优化复杂度。

我们想起一种 dp 优化方法,在值域很大,另一维很小,答案满足为关于值域的较小次数多项式时,可以用拉格朗日插值优化。例如这题

类似地,这题我们放大眼睛观察,我们发现任意摆放线段时,合法的方案数是一个关于 \(m\)\(n\) 次多项式。当我们钦定最长线段起点时,方案数就是一个关于 \(m\)\(n-1\) 次多项式但我不会证。感性理解一下,每次插入一条线段最多使次数增加 \(1\),而一共 \(n-1\) 条线段,所以多项式次数不超过 \(n-1\)

于是我们取 \(m=c,2c,\cdots,nc\) 即可得到这个多项式的 \(n\) 个点值,而我们要求:

\[\lim\limits_{m\to +∞}\frac{f(m)}{m^{n-1}} \]

上面是合法的方案数,下面是总方案数,除一下就是概率。同时不难发现这东西就是 \([m^{n-1}]f(m)\),即求最高次数项的系数

回忆拉格朗日插值公式:

\[f(x)=\sum\limits_{i=1}^ny_i\prod\limits_{j\neq i}\frac{x-x_j}{x_i-x_j} \]

相当于若干个一次式相乘,每个一次式里面取一次项或常数项。但是由于我们要求最高次数项,我们只能取一次项,所以:

\[[x^{n-1}]f(x)=\sum\limits_{i=1}^ny_i\prod\limits_{j\neq i}\frac{1}{x_i-x_j} \]

代入 \(y_i=f_{ic,U}\) 就做完了。复杂度 \(O(c^2n^42^n)\),其实由于钦定的最长线段,应该是 \(2^{n-1}\),所以常数很小。

其实不会拉插的话可以高斯消元。复杂度不变。

const int N = 10;
const int M = 55;
const int T = (1 << 5) + 10;
int n, c, tc, S, l[N], tl[N], f[N * M][T], y[N];

signed main() {
    n = rd(), tc = rd(), S = (1 << (n - 1)) - 1;
    for (int i = 0; i < n; i++) tl[i] = rd();
    sort(tl, tl + n);
    for (int _ = 1; _ <= n; _++) {
        for (int i = 0; i < n; i++) l[i] = tl[i] * _;
        c = tc * _;
        for (int i = 0; i <= c; i++)
            memset(f[i], 0, sizeof(int) * (S + 5));
        f[l[n - 1]][0] = 1;
        for (int i = 0; i <= c; i++) 
            for (int j = 0; j < n - 1; j++) 
                for (int k = 0; k < S; k++) 
                    if ((k >> j) & 1 ^ 1) 
                        for (int w = max(i, l[n - 1]); w <= c; w++) 
                            f[min(max(w, i + l[j]), c)][k | (1 << j)] += f[w][k];
        y[_] = f[c][S];
    }
    double res = 0;
    for (int i = 1; i <= n; i++) {
        int p = y[i], q = 1;
        for (int j = 1; j <= n; j++) 
            if (j ^ i) q *= (i - j) * tc;
        res += 1. * p / q;
    }
    printf("%.12lf", res);
    return 0;
}