【SSL 2403】图床(三分)
图床
题目链接:SSL 2403
题目大意
有 n 个物品,进行 m 次操作每次会随机选一个展示。
然后给你 n 个范围,要你猜测这个 n 的值。
思路
首先根据于假设一个 \(n\) 然后求实际可能的 \(n\) 值其实不行,因为你 \(n\) 可能很多。
不妨直接考虑一个 \(n\) 是的概率。
那全部的展示可能是 \(n^m\)。
考虑对应上它给你的展示。
注意到你无法直接判断 \(n\) 的原因是可能有一些从来没有展示过。
那你考虑 \(n\) 个物品最后展示了 \(k\) 个的概率,或者这里我们求方案除总数就是概率。
那考虑怎么算,首先我们选 \(k\) 个物品展示,那展示有展示的顺序(这里是第一次出现的顺序)。
那考虑先放下这些第一次出现的,然后考虑放别的。
那你放在第一次出现中第 \(i\) 个后面就有 \(i\) 中物品可以选,那按顺序放的话就接在直接后面或者你放过的后面都可以是一样的。
那总共就是 \(1+2+...+k\) 中地方*对应选法,那就是 \(k(k+1)/2\) 个方案每个数。
那总方案数就是 \(\binom{n}{k}k!(k(k+1)/2)^{m-k}=\dfrac{n!}{(n-k)!}(\dfrac{k(k+1)}{2})^{m-k}\)
那概率就是:
\(\dfrac{n!}{(n-k)!n^m}(\dfrac{k(k+1)}{2})^{m-k}\)
那我们要找的就是概率最大的那个。
那可以做到 \(O(T(n\max-n\min))\),不过注意到很多东西都很大,不过只有乘法和除法,而且对精度的要求不高,可以把全部都转成指数加减,直接比较指数大小。
然后考虑到概率是数值,肯定是有偏序关系的,那考虑比较两个 \(n_1,n_2\) 的过程,假设 \(n_1\) 没有 \(n_2\) 优。
\(\dfrac{n_1!}{(n_1-k)!n_1^m}(\dfrac{k(k+1)}{2})^{m-k}<\dfrac{n_2!}{(n_2-k)!n_2^m}(\dfrac{k(k+1)}{2})^{m-k}\)
那注意到右边这一坨跟 \(n\) 压根没关系,可以直接不管。
\(\dfrac{n_1!}{(n_1-k)!n_1^m}<\dfrac{n_2!}{(n_2-k)!n_2^m}\)
\(n_1!(n_2-k)!n_2^m<n_2!(n_1-k)!n_1^m\)
那也就是我们只需要找到 \(\dfrac{n!}{(n-k)!n^m}\) 的最大值。
通过(猜结论 / 搞图像找规律 / 奇怪的数学证明),发现答案的这个东西是单峰的(当 \(\geqslant k\) 的时候)。(不想证了搞数学式子搞麻了)
然后三分即可。
代码
#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long
#define ull unsigned long long
using namespace std;
const int Size=1<<23,NN=1e5+5,M=65535;
char buf[Size],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++)
int re; char c;
int read() {
re = 0; c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re;
}
void readd() {
c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') c = getchar();
if (c == '.') {
c = getchar();
while (c >= '0' && c <= '9') c = getchar();
}
}
const int P = 1e5 + 1000;
int nmin, nmax, m, cnt;
ull a[P];
double jc[P], log2_[P];
bool f(int x, int y) {
return jc[x] + jc[y - cnt] + log2_[y] * m < jc[y] + jc[x - cnt] + log2_[x] * m;
}
int main() {
freopen("pic.in", "r", stdin);
freopen("pic.out", "w", stdout);
for (int i = 1; i < P; i++) {
log2_[i] = log10(i) / log10(2);
jc[i] = jc[i - 1] + log2_[i];
}
int T = read(); readd(); readd(); readd(); readd(); nmin = read(); nmax = read(); m = read();
while (T--) {
for (int i = 1; i <= m; i++) {
c = getchar(); while (c < 'a' || c > 'z') c = getchar();
ull now = 0;
while (c >= 'a' && c <= 'z') {
now = now * 26 + c - 'a';
c = getchar();
}
a[i] = now;
}
sort(a + 1, a + m + 1);
cnt = 1;
for (int i = 2; i <= m; i++)
if (a[i] != a[i - 1]) cnt++;
int L = max(cnt, nmin), R = nmax;
while (R - L + 1 >= 2) {
int mid = (L + R) >> 1;
if (f(mid, mid + 1)) L = mid + 1;
else R = mid;
}
int re = L;
for (int i = L; i < R; i++) if (f(re, i + 1)) re = i + 1;
printf("%d\n", re);
}
return 0;
}