n ≤ 2333 n\le2333 n≤2333 将最后形成的三元组分类 三个一样的,两个一样的,三个都不一样的分别计算就比较方便 三个一样的和两个一样的可以 O ( n ) O(n) O(n) 三个都不一样显然枚举两个然后算出另一个
关于手写一些可以实现 m a p map map功能的东西 可以用一个类似邻接表的东西存
int first[Mod], nxt[N], to[N], val[N], tot; int ask(int p){ int x = p % Mod; for(int i = first[x]; i; i = nxt[i]) if(to[i] == p) return val[i]; return 0; } void add(int p, int v){ int x = p % Mod; for(int i = first[x]; i; i = nxt[i]){ if(to[i] == p) {val[i] += v; return;} } nxt[++tot] = first[x], first[x] = tot, to[tot] = p, val[tot] = v; }也可以冲突就暴力挪位
struct MAP{ static cs int Mod = 1e7+9; int key[Mod], val[Mod]; Map(){ memset(key, -1, sizeof(key)); } int pos(int k){ int h = k % Mod; while(key[h] != -1 && key[h] != k) h = h+1 == Mod ? 0 : h+1; return h; } void ins(int k){ int h = pos(k); if(key[h] == -1) key[h] = k, val[h] = 1; else val[h]++; } int find(int k){ int h = pos(k); return key[h] == -1 ? 0 : val[h]; } };也可以离散化然后暴力二分
struct MAP{ int b[N], siz, val[N]; void build(){ sort(b + 1, b + siz + 1); siz = unique(b + 1, b + siz + 1) - (b + 1); } int find(int x){ int l = 1, r = siz; while(l < r){ int mid = (l+r) >> 1; if(b[mid] >= x) r = mid; else l = mid + 1; } return l; } void add(int x){ val[find(x)]++; } int ask(int x){ int l = find(x); if(b[l] == x) return val[l]; return 0; } };然后发现快速幂的复杂度是满的 l o g ( 1 e 9 ) log(1e9) log(1e9),可以改成常数更小的 e x g c d exgcd exgcd 还可以预处理逆元,然后 n 2 n^2 n2 枚举的时候就不用快速幂只剩下一个常数很小的二分 复杂度 O ( n 2 l o g ( n ) ) O(n^2log(n)) O(n2log(n))
#include<bits/stdc++.h> #define cs const using namespace std; namespace IO{ inline char gc(){ static cs int Rlen = 1 << 22 | 1; static char buf[Rlen], *p1, *p2; return (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, Rlen, stdin), p1 == p2) ? EOF : *p1++; } int read(){ int cnt = 0, f = 1; char ch = 0; while(!isdigit(ch)) { ch = gc(); if(ch == '-') f = -1;} while(isdigit(ch)) cnt = cnt * 10 + (ch-'0'), ch = gc(); return cnt * f; } } using namespace IO; typedef long long ll; cs int N = 2500; int n, p, a[N], b[N], siz, inv[N]; ll ans; int add(int a, int b){ return a + b >= p ? a + b - p : a + b; } int mul(int a, int b){ return 1ll * a * b % p; } int ksm(int a, int b){ int ans=1; for(;b;b>>=1, a=mul(a,a)) if(b&1) ans=mul(ans,a); return ans; } bool vis[N]; int ps[N], tim[N]; struct MAP{ int b[N], siz; int val[N]; void build(){ sort(b + 1, b + siz + 1); siz = unique(b + 1, b + siz + 1) - (b + 1); } int find(int x){ int l = 1, r = siz; while(l < r){ int mid = (l+r) >> 1; if(b[mid] >= x) r = mid; else l = mid + 1; } return l; } void add(int x){ val[find(x)]++; } int ask(int x){ int l = find(x); if(b[l] == x) return val[l]; return 0; } }bin, sum; int main(){ n = read(), p = read(); for(int i = 1; i <= n; i++){ a[i] = read(); bin.b[++bin.siz] = a[i]; sum.b[++sum.siz] = a[i] % p; b[++siz] = a[i]; inv[i] = ksm(a[i] % p, p - 2); } sort(b + 1, b + siz + 1); siz = unique(b + 1, b + siz + 1) - (b + 1); bin.build(); sum.build(); for(int i = 1; i <= n; i++) ps[i] = bin.find(a[i]); for(int i = 1; i <= n; i++){ if(!tim[ps[i]]) sum.add(a[i] % p); tim[ps[i]]++; } // three of them are the same for(int i = 1; i <= n; i++){ if(tim[ps[i]] >= 3 && !vis[ps[i]]){ vis[ps[i]] = true; if(mul(inv[i], mul(inv[i], inv[i])) == 1) ++ans; } } // two of them are the same memset(vis, 0, sizeof(vis)); for(int i = 1; i <= n; i++){ if(tim[ps[i]] >= 2 && !vis[ps[i]]){ vis[ps[i]] = true; int nx = mul(inv[i], inv[i]); ans += sum.ask(nx); if(a[i] % p == nx) ans--; } } // each of them is different memset(vis, 0, sizeof(vis)); ll ret = 0; for(int i = 1; i <= siz; i++) inv[i] = ksm(b[i] % p, p - 2); for(int i = 1; i <= siz; i++){ for(int j = i + 1; j <= siz; j++){ int ni = b[i] % p, nj = b[j] % p; int nx = mul(inv[i], inv[j]); ret += sum.ask(nx); if(ni == nx) ret--; if(nj == nx) ret--; } } cout << ans + ret / 3; return 0; }n ≤ 1 e 5 n\le 1e5 n≤1e5 考场的垃圾做法: 发现物品按 w w w 排序就是选一个 v v v 的最长不下降序列并且满足选出来的每一个 w w w 在另一边都有一个 ≥ \ge ≥ 它的映射,另一边也排个序, f i , j f_{i,j} fi,j 表示这边选到 i i i另一边选到 j j j 的最长长度 f i , j = m a x ( f p , k ) ( v p ≤ v i , k ≤ j − 1 ) f_{i,j}=max(f_{p,k})(v_p\le v_i,k\le j-1) fi,j=max(fp,k)(vp≤vi,k≤j−1),二维树状数组优化转移
正解:考虑贪心,显然如果最后的长度是 l e n len len 的话选最大的 l e n len len 个一定是最优的 考虑对每一个 i i i 求出它向后的最长长度 l e n len len 并且求出可以装下 i i i 的个数 c n t cnt cnt,那么合法的最长长度就是 m i n ( l e n , c n t ) min(len,cnt) min(len,cnt),从后向前 d p dp dp,动态维护装的下的个数,树状数组优化转移即可 有时候不能莽起 dp,还是要从贪心的角度考虑
#include<bits/stdc++.h> #define cs const using namespace std; int read(){ int x = 0, f = 1; char c = 0; while(!isdigit(c)){ c = getchar(); if(c == '-')f = -1;} while(isdigit(c)) x = x*10 + (c-'0'), c = getchar(); return x * f; } typedef long long ll; cs int N = 1e5 + 5; int T, n, m, t[N], b[N], siz; struct BAG{ int w, v;}a[N]; bool cmp(BAG a, BAG b){ return a.w < b.w || (a.w == b.w && a.v < b.v); } struct BIT{ int c[N]; void clear(){ memset(c, 0, sizeof(c)); } void add(int x, int v){ for(;x;x-=x&-x) c[x] = max(c[x], v); } int ask(int x){ int ans=0; for(;x<=siz;x+=x&-x) ans = max(ans, c[x]); return ans; } }bit; void Solve(){ bit.clear(); siz = 0; n = read(); for(int i = 1; i <= n; i++){ a[i].w = read(); a[i].v = read(); b[++siz] = a[i].v; } sort(a + 1, a + n + 1, cmp); reverse(a + 1, a + n + 1); sort(b + 1, b + siz + 1); siz = unique(b + 1, b + siz + 1) - (b + 1); m = read(); for(int i = 1; i <= m; i++){ t[i] = read(); } sort(t + 1, t + m + 1); reverse(t + 1, t + m + 1); int ans = 0, p = 0; for(int i = 1; i <= n; i++){ while(p + 1 <= m && t[p + 1] >= a[i].w) ++p; int x = lower_bound(b + 1, b + siz + 1, a[i].v) - b; int f = min(bit.ask(x) + 1, p); bit.add(x, f); ans = max(ans, f); } cout << ans << '\n'; } int main(){ T = read(); while(T--) Solve(); return 0; }n ≤ 500 n\le 500 n≤500
考场的垃圾做法: f s i z , d e p f_{siz,dep} fsiz,dep 表示大小和深度为 s i z , d e p siz,dep siz,dep 的方案数,暴力枚举拆分,以及拆分出来的深度 f s i z , d e p = ∑ i = 1 s i z − 1 ∑ j = 1 s i z − i − 1 f s i z − i − 1 , j ∑ k = 1 i f i , k [ m a x ( j , k + 1 ) = 1 ] f_{siz,dep}=\sum_{i=1}^{siz-1}\sum_{j=1}^{siz-i-1}f_{siz-i-1,j}\sum_{k=1}^{i}f_{i,k}[max(j,k+1)=1] fsiz,dep=∑i=1siz−1∑j=1siz−i−1fsiz−i−1,j∑k=1ifi,k[max(j,k+1)=1] 然后还要重新分配编号,考场上思维不严谨的我写的 ( s i z − 1 k ) \binom{siz-1}{k} (ksiz−1) 请睁大眼睛看一下我的预处理
c[0][0] = 1; for(int i = 1; i <= n; i++){ c[i][i] = 1; for(int j = 1; j <= i; j++){ c[i][j] = add(c[i-1][j], c[i-1][j-1]); } }手玩一下发现这样预处理正好把杨辉三角挪了一位,所以实际上乘的系数是 ( s i z − 2 k − 1 ) \binom{siz-2}{k-1} (k−1siz−2) 而 s t d std std 正好成的是 ( s i z − 2 k − 1 ) \binom{siz-2}{k-1} (k−1siz−2),然后我就歪打正着了(雾) 言归正传,钦定是原来的取到 d e p dep dep 还是后来拼接的取到 d e p dep dep 就可以前缀和优化转移
说一下乘 ( s i z − 1 k ) \binom{siz-1}{k} (ksiz−1) 为什么有问题,因为当前拼接的子树很有可能是跟之前的同构,显然会算重 我们直接钦定当前拼接上去的子树的根是次小,这样相当于对儿子进行了排序,这样就不会算重了 既然钦定了根是最小,当前子树的根是次小,那么方案数就是将剩下的重新排列的方案数即 ( s i z − 2 k − 1 ) \binom{siz-2}{k-1} (k−1siz−2) 然后发现前缀和非常丑,直接按套路把状态定义为大小为 s i z siz siz 深度 ≤ d e p \le dep ≤dep 的方案数 f s i z , d e p = ∑ i = 1 s i z − 1 f s i z − i − 1 , d e p ∗ f i , d e p − 1 ∗ ( s i z − 2 i − 1 ) f_{siz,dep}=\sum_{i=1}^{siz-1}f_{siz-i-1,dep}*f_{i,dep-1}*\binom{siz-2}{i-1} fsiz,dep=i=1∑siz−1fsiz−i−1,dep∗fi,dep−1∗(i−1siz−2)
考虑直接枚举子树的拼接,枚举子树个数以及每个子树的大小 f [ d ] [ n ] = ∑ k = 1 n − 1 1 k ! ∑ ∑ i = 1 k b i = n − 1 ( n − 1 ) ! ∏ b i ! ∏ i = 1 k f [ d − 1 ] [ b i ] f[d][n]=\sum_{k=1}^{n-1}\frac{1}{k!}\sum_{\sum_{i=1}^k b_i=n-1}\frac{(n-1)!}{\prod b_i!}\prod_{i=1}^kf[d-1][b_i] f[d][n]=k=1∑n−1k!1∑i=1kbi=n−1∑∏bi!(n−1)!i=1∏kf[d−1][bi] f [ d ] [ n ] ( n − 1 ) ! = ∑ k = 1 n − 1 1 k ! ∑ ∑ i = 1 k b i = n − 1 1 ∏ b i ! ∏ i = 1 k f [ d − 1 ] [ b i ] \frac{f[d][n]}{(n-1)!}=\sum_{k=1}^{n-1}\frac{1}{k!}\sum_{\sum_{i=1}^k b_i=n-1}\frac{1}{\prod b_i!}\prod_{i=1}^kf[d-1][b_i] (n−1)!f[d][n]=k=1∑n−1k!1∑i=1kbi=n−1∑∏bi!1i=1∏kf[d−1][bi] 注意到后面一坨的组合意义是集合间带标号的拼接,上指数型生成函数 令 f d ( x ) = ∑ i = 0 ∞ f [ d ] [ i ] i ! x i f_d(x)=\sum_{i=0}^{\infty}\frac{f[d][i]}{i!}x^i fd(x)=∑i=0∞i!f[d][i]xi ∑ n = 0 ∞ f [ d ] [ n ] ( n − 1 ) ! x n − 1 = ∑ n = 0 ∞ ∑ k = 1 n − 1 1 k ! ∑ ∑ i = 1 k b i = n − 1 ∏ i = 1 k f [ d − 1 ] [ b i ] b i ! x b i \sum_{n=0}^{\infty}\frac{f[d][n]}{(n-1)!}x^{n-1}=\sum_{n=0}^{\infty}\sum_{k=1}^{n-1}\frac{1}{k!}\sum_{\sum_{i=1}^k b_i=n-1}\prod_{i=1}^k\frac{f[d-1][b_i]}{b_i!}x^{b_i} n=0∑∞(n−1)!f[d][n]xn−1=n=0∑∞k=1∑n−1k!1∑i=1kbi=n−1∑i=1∏kbi!f[d−1][bi]xbi = ∑ k = 1 ∞ 1 k ! ∑ b i = 0 ∞ ∏ i = 1 k f [ d − 1 ] [ b i ] b i ! x b i =\sum_{k=1}^{\infty} \frac{1}{k!}\sum_{b_i=0}^{\infty}\prod_{i=1}^k\frac{f[d-1][b_i]}{b_i!}x^{b_i} =k=1∑∞k!1bi=0∑∞i=1∏kbi!f[d−1][bi]xbi = ∑ k = 1 ∞ 1 k ! ∏ i = 1 k ∑ b i = 0 ∞ f [ d − 1 ] [ b i ] b i ! x b i =\sum_{k=1}^{\infty} \frac{1}{k!}\prod_{i=1}^k\sum_{b_i=0}^{\infty}\frac{f[d-1][b_i]}{b_i!}x^{b_i} =k=1∑∞k!1i=1∏kbi=0∑∞bi!f[d−1][bi]xbi = ∑ k = 1 ∞ 1 k ! f d − 1 ( x ) k = e x p ( f d − 1 ( x ) ) =\sum_{k=1}^{\infty} \frac{1}{k!}f_{d-1}(x)^k=exp(f_{d-1}(x)) =k=1∑∞k!1fd−1(x)k=exp(fd−1(x)) 而 ∑ n = 0 ∞ f [ d ] [ n ] ( n − 1 ) ! x n − 1 = f d ( x ) ′ = e x p ( f d − 1 ( x ) ) \sum_{n=0}^{\infty}\frac{f[d][n]}{(n-1)!}x^{n-1}=f_d(x)'=exp(f_{d-1}(x)) n=0∑∞(n−1)!f[d][n]xn−1=fd(x)′=exp(fd−1(x)) 所以 f d ( x ) = ∫ e x p ( f d − 1 ( x ) ) d x f_d(x)=\int exp(f_{d-1}(x)) dx fd(x)=∫exp(fd−1(x))dx 多项式全家桶
