[状压DP思路妙题][AGC020F]Arcs on a Circle

  • 题目传送门

  • 大致题意:一个长度为 \(C\) 的圆周上有 \(N\) 条弧,第 \(i\) 条弧的长度为 \(L_i\),满足 \(1\le L_i<C\),位置在圆周上均匀随机,求这些弧覆盖整个圆周的概率,\(1\le N\le 6\),\(1\le C\le 50\)

  • 先断环为链,断点位于最长的弧的起点处,至于为什么是最长的弧,后面会说

  • 把这个断点处的位置设为 \(0\),注意到对于剩下的 \(N-1\) 条弧,如果这些弧起点位置的小数部分确定了,就可以转化成整数部分在 \([0,C)\) 间均匀随机,实现把弧位置取值从连续转化成离散

  • 而真正有影响的是小数部分的相对大小,故可以暴力枚举 \((N-1)!\) 种相对大小关系(第一条弧的小数部分为 \(0\))

  • 问题转化为长度为 \(NC\) 的圆周,有 \(N\) 条弧(从 \(0\) 开始计数),第 \(0\) 条弧的位置已经钦定,第 \(i\) 条弧的起点模 \(N\) 为 \(i\),覆盖整个圆周的方案数

  • 考虑 DP:\(f_{i,j,S}\) 表示起点在 \([0,i]\) 内,覆盖到最远的点为 \(j\),用掉的弧集合为 \(S\) 的方案数,保证 \(i<j\)

  • 转移两种情况:

  • (1)\(i+1\) 不作为起点:

  • \[f_{i+1,j,S}+=f_{i,j,S} \]

  • (2)\(i+1\) 作为弧 \(p=(i+1)\bmod N\) 的起点:

  • \[f_{i+1,\min(NC,\max(j,i+1+N\times L_p)),S\cup p}+=f_{i,j,S} \]

  • 算上枚举小数部分,复杂度 \(O((N-1)!(NC)^22^N)\)

  • 注意到这是一个环,这个 DP 看上去正确性不太对(左边的点会被右边的弧覆盖),但如果让 \(L_0\) 最大,则简单讨论一下可得每个点一定被至少一条起点标号比它小的弧覆盖,这个 DP 的正确性得以保证,这就是要让 \(L_0\) 最大的原因

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

typedef long double ld;

const int N = 8, M = 305, T = 34;

int n, C, l[N], p[N], f[M][M][T];
ld ans;

int main()
{
	read(n); read(C);
	for (int i = 1; i <= n; i++) read(l[i]), p[i] = i;
	std::sort(l + 1, l + n + 1);
	do
	{
		memset(f, 0, sizeof(f));
		f[0][l[n] * n][0] = 1;
		for (int i = 1; i < n * C; i++)
			for (int j = i; j <= n * C; j++)
				for (int S = 0; S < (1 << n - 1); S++)
				{
					f[i][j][S] += f[i - 1][j][S];
					int x = i % n; if (!x || ((S >> x - 1) & 1)) continue;
					f[i][Min(n * C, Max(j, i + l[p[x]] * n))]
						[S | (1 << x - 1)] += f[i - 1][j][S];
				}
		ans += f[n * C - 1][n * C][(1 << n - 1) - 1];
	} while (std::next_permutation(p + 1, p + n));
	for (int i = 1; i < n; i++) ans /= i * C;
	return printf("%.16Lf\n", ans), 0;
}
上一篇:PTA路径判断


下一篇:Codechef SEAARC Sereja and Arcs (分块、组合计数)