「2019纪中集训Day15」解题报告
T1、投票
\(n \ (n \leq 2000)\) 个事件,第 \(i\) 件事发生的概率为 \(p_i\),选出 \(k \ (k \leq 2000, 2 | k)\) 件事,求使得发生与不发生的事件数量相同的最大概率;
精度误差需保证在 \(10^{-6}\) 以内。
\(Sol\):
一定是选按概率排好序的一段前缀和一段后缀比较优秀,这一点并不显然。
\(prf\):
假定现在已经确定了 \(k - 1\) 个事件,未确定的事件设为 \(x\);
那么概率为:
这是一个关于 \(p_x\) 的一次幂函数,所以一定是取能取到的最大或最小值比较优。
\(Q.E.D.\)
先将概率从小到大排序;
记 \(f_{i,j}\) 表示前 \(i\) 件事恰好发生 \(j\) 件的概率,转移是显然的;
记一个前缀和一个后缀的 \(dp\),再枚举一边选择了多少人,求出概率取最大即可。
\(Source\):
//#pragma GCC optimize(3,"Ofast","inline")
#include
#include
typedef double db;
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 2e3 + 5;
int n, m;
db pre[N][N], suf[N][N], p[N], res;
int main() {
//freopen("in", "r", stdin);
freopen("vote.in", "r", stdin);
freopen("vote.out", "w", stdout);
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%lf", &p[i]);
std::sort(p + 1, p + 1 + n);
pre[0][0] = 1;
for (int i = 1; i <= m; ++i) {
pre[i][0] = pre[i - 1][0] * (1 - p[i]);
for (int j = 1; j <= i; ++j)
pre[i][j] = pre[i - 1][j - 1] * p[i] + pre[i - 1][j] * (1 - p[i]);
}
std::reverse(p + 1, p + 1 + n);
suf[0][0] = 1;
for (int i = 1; i <= m; ++i) {
suf[i][0] = suf[i - 1][0] * (1 - p[i]);
for (int j = 1; j <= i; ++j)
suf[i][j] = suf[i - 1][j - 1] * p[i] + suf[i - 1][j] * (1 - p[i]);
}
for (int i = 0; i <= m; ++i) {
db tmp = 0;
for (int j = std::max(0, m / 2 - (m - i)); j <= i && j <= m / 2; ++j)
tmp += pre[i][j] * suf[m - i][m / 2 - j];
chk_max(res, tmp);
}
printf("%.7lf\n", res);
return 0;
}
T2、动态数点
给定一个长度为 \(n \ (n \leq 5 \times 10 ^ 5)\) 的序列 \(\{ a_i \} \ (a_i \leq 2 ^ {32} - 1)\),如果对于一个区间 \([l, r]\),存在 \(k \in [l, r]\) 使得 \(\forall i \in [l,r] \ a_k | a_i\),那么它有 \(r - l\) 的价值,求最大价值。
\(Sol\):
\(gcd\) 的 \(\log\) 比较小,直接 \(st\) 表可过。
时间复杂度 \(O(n \log_2 n \log_2a_i)\)。
对于两个区间里可以作为除数的数 \(a_x, a_y \ (x \ne y)\);
若 \(a_x = a_y\),他们一定会被包含在同一个区间;
否则包含其中一个点的区间不会包含另一个点,很容易证明这两点。
所以从左到右枚举除数 \(a_i\),向左右扩展,找到包含它的极大区间 \([l, r]\),更新答案,并把除数设为 \(a_{r + 1}\) 即可;
根据上述引理,\(l\) 一定在上一个除数的位置的右边,所以每个点只会被算到两次。
时间复杂度 \(O(n)\)。
\(Source\):
#include
#include
#include
unsigned in() {
unsigned x = 0; char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return x;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 5e5 + 5;
unsigned n, a[N], res[N];
int main() {
//freopen("in", "r", stdin);
freopen("point.in", "r", stdin);
freopen("point.out", "w", stdout);
n = in();
for (unsigned i = 1; i <= n; ++i)
a[i] = in();
unsigned max = 0, num = 0, tmp;
for (unsigned i = 1, l, r; i <= n; i = r + 1) {
l = r = i;
while (r < n && a[r + 1] % a[i] == 0)
++r;
while (l > 1 && a[l - 1] % a[i] == 0)
--l;
tmp = r - l;
if (tmp == max)
res[++num] = l;
if (tmp > max)
max = tmp, res[num = 1] = l;
}
printf("%u %u\n", num, max);
for (unsigned i = 1; i <= num; ++i)
printf("%u ", res[i]);
puts("");
return 0;
}
T3、演员
一道让我咕博客的题,然而理解后也咕了很久十分简单。
老虎蒜头神仙题。
给 \(m \ (m \leq 300)\) 个演员排 \(n \ (n \leq 10 ^ 6)\) 天班;
每次选择一个演员,并选择区间 \([l, r]\),给他安排 \([l,r]\) 里没有还被分配给其他演员的天;
每天都要被分配,演员可以不被安排,求分配方案数,对 \(10 ^ 9 + 7\) 取模。
\(Sol\):
显然可以发现,两个演员被安排的天一定不存在相交但不包含的情况;
那么按时间顺序来看正在工作的演员,可以把它看成一个栈,工作的演员在栈顶;
可以 \(dp\),记 \(f_{i,j,k}\) 表示前 \(i\) 天,已经有 \(j\) 个演员入过栈,栈里还有 \(k\) 个人的方案数,转移是显然的,不再赘述。
由于 \(n\) 很大,这样显然不行,但是注意到时间被不同演员分成了最多 \(2m - 1\) 段;
可以把第一维状态改为上述时间段,对每个状态组合数计算贡献即可。
时间复杂度 \(O(m ^ 3)\)。
\(Source\):
//#pragma GCC optimize(3,"Ofast","inline")
#include
#include
#include
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int M = 305, mod = 1e9 + 7;
int n, m, res;
int fac[N], inv[N], f[M + M][M][M];
inline void add(int &_, int __) {
_ += __;
if (_ >= mod)
_ -= mod;
}
int qpow(int base, int b, int ret = 1) {
for (; b; b >>= 1, base = 1ll * base * base % mod)
if (b & 1)
ret = 1ll * ret * base % mod;
return ret;
}
void prep() {
int nn = std::max(n, m);
fac[0] = inv[0] = 1;
for (int i = 1; i <= nn; ++i)
fac[i] = 1ll * fac[i - 1] * i % mod;
inv[nn] = qpow(fac[nn], mod - 2);
for (int i = nn - 1; i; --i)
inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
inline int C(const int n, const int m) {
return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
inline int A(const int n, const int m) {
return 1ll * fac[n] * inv[n - m] % mod;
}
int main() {
//freopen("in", "r", stdin);
freopen("actor.in", "r", stdin);
freopen("actor.out", "w", stdout);
n = in(), m = in();
prep();
f[0][0][0] = 1;
for (int i = 1; i <= n && i <= m + m; ++i) {
for (int j = 1; j <= m; ++j)
for (int k = j, sum = 0, tmp; k; --k) {
tmp = f[i - 1][j - 1][k - 1]; add(tmp, sum);
add(f[i][j][k], tmp);
add(sum, f[i - 1][j][k]);
add(res, 1ll * A(m, j) * C(n - 1, i - 1) % mod * f[i][j][k] % mod);
}
}
printf("%d\n", res);
return 0;
}