【SSL 2403】图床(三分)

时间:2022-08-28 01:11:15

图床

题目链接:SSL 2403

题目大意

有 n 个物品,进行 m 次操作每次会随机选一个展示。
然后给你 n 个范围,要你猜测这个 n 的值。

思路

首先根据于假设一个 n n n 然后求实际可能的 n n n 值其实不行,因为你 n n n 可能很多。
不妨直接考虑一个 n n n 是的概率。

那全部的展示可能是 n m n^m nm
考虑对应上它给你的展示。
注意到你无法直接判断 n n n 的原因是可能有一些从来没有展示过。
那你考虑 n n n 个物品最后展示了 k k k 个的概率,或者这里我们求方案除总数就是概率。

那考虑怎么算,首先我们选 k k k 个物品展示,那展示有展示的顺序(这里是第一次出现的顺序)。
那考虑先放下这些第一次出现的,然后考虑放别的。
那你放在第一次出现中第 i i i 个后面就有 i i i 中物品可以选,那按顺序放的话就接在直接后面或者你放过的后面都可以是一样的。
那总共就是 1 + 2 + . . . + k 1+2+...+k 1+2+...+k 中地方*对应选法,那就是 k ( k + 1 ) / 2 k(k+1)/2 k(k+1)/2 个方案每个数。
那总方案数就是 ( n k ) k ! ( k ( k + 1 ) / 2 ) m − k = n ! ( n − k ) ! ( k ( k + 1 ) 2 ) m − k \binom{n}{k}k!(k(k+1)/2)^{m-k}=\dfrac{n!}{(n-k)!}(\dfrac{k(k+1)}{2})^{m-k} (kn)k!(k(k+1)/2)mk=(nk)!n!(2k(k+1))mk

那概率就是:
n ! ( n − k ) ! n m ( k ( k + 1 ) 2 ) m − k \dfrac{n!}{(n-k)!n^m}(\dfrac{k(k+1)}{2})^{m-k} (nk)!nmn!(2k(k+1))mk

那我们要找的就是概率最大的那个。
那可以做到 O ( T ( n max ⁡ − n min ⁡ ) ) O(T(n\max-n\min)) O(T(nmaxnmin)),不过注意到很多东西都很大,不过只有乘法和除法,而且对精度的要求不高,可以把全部都转成指数加减,直接比较指数大小。

然后考虑到概率是数值,肯定是有偏序关系的,那考虑比较两个 n 1 , n 2 n_1,n_2 n1,n2 的过程,假设 n 1 n_1 n1 没有 n 2 n_2 n2 优。
n 1 ! ( n 1 − k ) ! n 1 m ( k ( k + 1 ) 2 ) m − k < n 2 ! ( n 2 − k ) ! n 2 m ( k ( k + 1 ) 2 ) m − k \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} (n1k)!n1mn1!(2k(k+1))mk<(n2k)!n2mn2!(2k(k+1))mk
那注意到右边这一坨跟 n n n 压根没关系,可以直接不管。
n 1 ! ( n 1 − k ) ! n 1 m < n 2 ! ( n 2 − k ) ! n 2 m \dfrac{n_1!}{(n_1-k)!n_1^m}<\dfrac{n_2!}{(n_2-k)!n_2^m} (n1k)!n1mn1!<(n2k)!n2mn2!
n 1 ! ( n 2 − k ) ! n 2 m < n 2 ! ( n 1 − k ) ! n 1 m n_1!(n_2-k)!n_2^m<n_2!(n_1-k)!n_1^m n1!(n2k)!n2m<n2!(n1k)!n1m

那也就是我们只需要找到 n ! ( n − k ) ! n m \dfrac{n!}{(n-k)!n^m} (nk)!nmn! 的最大值。
通过(猜结论 / 搞图像找规律 / 奇怪的数学证明),发现答案的这个东西是单峰的(当 ⩾ k \geqslant k 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;
}