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

    科技2022-07-10  114

    欢乐

    求最小的 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 的花也可以上线段树哈希。

    Processed: 0.013, SQL: 8