CSP-S2考前综合强化刷题 Day3

欢乐

求最小的 K K K 使得 K ! ≥ N ! K ! K!\geq\frac{N!}{K!} K!K!N!

log ⁡ a ( M × N ) = log ⁡ a M + log ⁡ a N log ⁡ a M N = log ⁡ a M − log ⁡ a N \log _{a}(M \times N)=\log _{a} M+\log _{a} N \\ \\ \log _{a} \frac{M}{N}=\log _{a} M-\log _{a} N loga(M×N)=logaM+logaNlogaNM=logaMlogaN

比较阶乘的大小可以取对数,因为 log ⁡ ( a ) < log ⁡ ( b )    ⟺    a < b \log(a) < \log(b) \iff a < b log(a)<log(b)a<b。那么

ln ⁡ n ! = ln ⁡ ∏ i = 1 n i = ∑ i = 1 n ln ⁡ ( i )   ln ⁡ n ! k ! = ln ⁡ ( n ) − ln ⁡ ( k ) \ln n! = \ln \prod_{i=1}^n i \\= \sum_{i=1}^n \ln(i) \\ \ \\ \ln\frac{n!}{k!} = \ln(n) -\ln(k) lnn!=lni=1ni=i=1nln(i) lnk!n!=ln(n)ln(k)

所以用数学库中的 log 函数预处理前缀和,二分一个 k k k 判断一下。

能判断阶乘的大小,是不是也可以判断组合数的大小?

水题

b:[a]:2;c:[a]:3;a:[]:1/2

//依赖限制可以直接模拟,最好看成拓扑排序
#include <bits/stdc++.h>
using namespace std;
const int N = 2000 + 5, INF = 0x3f3f3f3f;
inline int read() {
	int x = 0, f = 0; char ch = 0;
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}
map <string, int> mp;
string s;
struct node {
	int need, num;
	vector <int> rely;
	string name;
	int ac;
}a[N];
string name[N];
int len, n, dian, cnt, lim, now;
bool cmp(node a, node b) {
	if (a.ac != b.ac) return a.ac < b.ac;
	return a.name < b.name;
}
bool kil[N], vis[N];
int id[N];
int cd[N];
int okt[N];
signed main() {
	cin >> s;
	len = s.size(), n = 0, dian = 0, cnt = 0;
	for (int i = 0; i < len; i++) {
		if (s[i] == ';') dian = i + 1;
		if (s[i] == ';' || s[i] == '/') {
			int d = 0;
			for (int j = i - 1, mul = 1; j >= 0 && isdigit(s[j]); j--, mul *= 10) d += mul * (s[j] - '0');
			a[++cnt].need = d;
		}
		if (s[i] == ':' && dian != -1) a[++n].name = s.substr(dian, i - dian), dian = -1;
		if (s[i] == '/') {
			for (int j = i + 1; j < len; j++) lim = lim * 10 + (s[j] - '0');
		}
	}
	for (int i = 1; i <= n; i++) mp[a[i].name] = i;
	int kh = 0; cnt = 0;
	for (int i = 0; i < len; i++) {
		if (s[i] == '[') kh = i, cnt++;
		if (s[i] == ']') {
			string t = ""; 
			for (int j = kh + 1; j <= i; j++) {
				if (s[j] == ',' || s[j] == ']' && t != "") {
					a[cnt].rely.push_back(mp[t]);
					t = "";
				}
				else t += s[j];
			}
		}
	}
	int ans = 0, ok = 1;
	for (int i = 1; i <= n; i++) a[i].num = i;
	while (ok) {
		vector <node> cur;
		for (int i = 1; i <= n; i++) {
			if (kil[i] || vis[i]) continue;
			bool flg = 1;
			int maxn = -1;
			for (int j = 0; j < a[i].rely.size(); j++) {
				if (!kil[a[i].rely[j]]) {
					flg = 0;
					break;
				}	
				maxn = max(maxn, okt[a[i].rely[j]]);
			}	
			if (flg) {
				a[i].ac = maxn;
				cur.push_back(a[i]);
			}
		}
		sort(cur.begin(), cur.end(), cmp);
		int p = 0;
		for (int i = 1; i <= lim; i++) if (!cd[i] && cur.size()) {
			node task = cur[0];
			cd[i] = task.need; 
			id[i] = cur[0].num;
			vis[id[i]] = 1;
			cur.erase(cur.begin());
		}
		int minn = INF;
		for (int i = 1; i <= lim; i++) if (cd[i]){
			minn = min(minn, cd[i]);
		}
		if (minn != INF) {
			for (int i = 1; i <= lim; i++) if (cd[i]) {
				cd[i] -= minn;
				if (cd[i] == 0) kil[id[i]] = 1, okt[id[i]] = ans + minn;
			}
			ans += minn;
		}
		ok = 0;
		for (int i = 1; i <= n; i++) 
			if (!kil[i]) {
				ok = 1;
				break;
			}
	}
	printf("%d\n", ans);
	return 0;
}

模拟

∑ l = 1 n ∑ r = l n f ( a l ∼ r ) \sum_{l=1}^n\sum_{r=l}^nf(a_{l\sim r}) l=1nr=lnf(alr) 其中 f f f 为 V 字三元组 i < j < k , a i > a j , a k > a j i < j < k,a_i > a_j,a_k >a_j i<j<k,ai>aj,ak>aj 的个数。

套路题,可惜没时间了,这告诉我们除了 T1 外其它题可能不按难度排序。

转换成每个三元组出现在多少个区间内,我们枚举中间数 x x x,那么两边的数都比它大,所以线段树预处理一下,假设 l l l 为左边的符合要求的数的下标和为 l l l,右边符合要求的数与 n n n 之差的和为 r r r,匹配一下答案是 l × r l \times r l×r。值得一提的是,两棵线段树要一个个加数来满足相对位置的限制。

其实这是一个对数字相对大小有限制的套路了,那么如果是对下标大小有限制呢。假设是 a 1 ∼ a n a_1 \sim a_n a1an,我们处理到了 i i i,如果 a i a_i ai 出现过了,那么线段树中 a i = 1 a_i = 1 ai=1,那么 1 ∼ i 1 \sim i 1i 的所有位置是 1 1 1 的,代表这个位置在 i i i 之前出现过,其它是 0 0 0 的位置那么在 i i i 之后出现过,当然也可以搞成一个桶相当于求出现的次数,这个 01 01 01 的花也可以上线段树哈希。

posted @ 2020-10-03 16:14  ylxmf2005  阅读(43)  评论(0)    收藏  举报