[TOC]node
<hr> [Practice Link](https://ac.nowcoder.com/acm/contest/881#question)c++
Solved | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
9/10 | O | Ø | Ø | Ø | Ø | O | - | Ø | Ø | O |
<hr>数组
题意: 定理两个序列等价,当且仅当:dom
$RMQ(w, l, r)$的定义是$[l, r]$区间内最小数的下标 如今给出两个序列$a_i, b_i$,保证每一个序列有$n$个互不相同的数,问找一下最大的$p$,使得${a_1, \cdots, a_p}$与${b_1, \cdots, b_p}$是等价的。ide
思路: 首先至关于找一个最长前缀,他们是等价的。 显然这个具备单调性。咱们能够去二分长度,而后就变成了一个断定问题。 根据定义,咱们要断定的是两个序列的任意一个子区间的$RMQ$都相同,那么反过来考虑。 咱们考虑一个数的管辖范围为管辖它左边离它最近的大于它的数之间的全部数,以及右边离他最近的大于它的数之间的全部数。 那么这个管辖范围内的任意一个子区间的$RMQ$都是这个数的下标。 那么也就是说,只要两个序列中,每一个数的管辖范围是相同的,那么这两个序列就是等价的。 求管辖范围能够用单调栈或者笛卡尔树。优化
代码:ui
#include <bits/stdc++.h> using namespace std; #define N 100010 int n, a[N], b[N]; struct Cartesian_Tree { struct node { int id, val, fa; int son[2]; node() {} node (int id, int val, int fa) : id(id), val(val), fa(fa) { son[0] = son[1] = 0; } }t[N]; int root, l[N], r[N]; void init() { t[0] = node(0, 0, 0); } void build(int n, int *a) { for (int i = 1; i <= n; ++i) { t[i] = node(i, a[i], 0); } for (int i = 1; i <= n; ++i) { int k = i - 1; while (t[k].val > t[i].val) { k = t[k].fa; } t[i].son[0] = t[k].son[1]; t[k].son[1] = i; t[i].fa = k; t[t[i].son[0]].fa = i; } root = t[0].son[1]; } int DFS(int u) { if (!u) return 0; l[t[u].id] = DFS(t[u].son[0]); r[t[u].id] = DFS(t[u].son[1]); return l[t[u].id] + r[t[u].id] + 1; } }t[2]; bool check(int x) { t[0].init(); t[1].init(); t[0].build(x, a); t[1].build(x, b); t[0].DFS(t[0].root); t[1].DFS(t[1].root); for (int i = 1; i <= x; ++i) { if (t[0].l[i] != t[1].l[i] || t[0].r[i] != t[1].r[i]) return 0; } return 1; } int main() { while (scanf("%d", &n) != EOF) { for (int i = 1; i <= n; ++i) { scanf("%d", a + i); } for (int i = 1; i <= n; ++i) { scanf("%d", b + i); } int l = 1, r = n, res = -1; while (r - l >= 0) { int mid = (l + r) >> 1; if (check(mid)) { res = mid; l = mid + 1; } else { r = mid - 1; } } printf("%d\n", res); } return 0; }
题意: 给出: $$ \begin{eqnarray*} \int_{0}^{\infty} \frac{1}{1 + x^2} dx = \frac{\pi}{2} \end{eqnarray*} $$ 询问 $$ \begin{eqnarray*} \frac{1}{\pi} \int_{0}^{\infty} \frac{1}{\prod\nolimits_{i = 1}^n (a_i^2 + x^2)} dx \end{eqnarray*} $$this
思路: 考虑$n = 1$时: $$ \begin{eqnarray*} \frac{1}{\pi} \int_{0}^{\infty} \frac{1}{a^2 + x^2} dx \end{eqnarray*} $$ 有不定积分: $$ \begin{eqnarray*} \int \frac{1}{a^2 + x^2} dx = \frac{1}{a} arctan(\frac{x}{a}) + C \end{eqnarray*} $$ 当$x \rightarrow \infty$时,就是$\frac{\pi}{2a}$。 那么考虑$n > 1$时,咱们知道有积分的和等于和的积分,因此咱们但愿把乘积拆成加减形式,那么能够用拆项积分法,比方说: $n = 2$时: $$ \begin{eqnarray*} \int \frac{1}{(a^2 + x^2)(b^2 + x^2)}dx \end{eqnarray*} $$ 考虑: $$ \begin{eqnarray*} &&\frac{1}{(a^2 + x^2)} \ &=& \frac{\alpha}{a^2 + x^2} + \frac{\beta}{b^2 + x^2} \ &=& \frac{\alpha(b^2 + x^2) + \beta(a^2 + x^2)}{(a^2 + x^2)(b^2 + x^2)} \ &=& \frac{b^2\alpha + a^2\beta + x^2(\alpha + \beta)}{(a^2 + x^2)(b^2 + x^2)} \end{eqnarray*} $$ 那么有: $$ \begin{eqnarray*} \left{ \begin{array}{cccc} b^2\alpha + a^2\beta = 1 \ \alpha + \beta = 0 \end{array} \right. \end{eqnarray*} $$ 因此: $$ \begin{eqnarray*} \left{ \begin{array}{cccc} \alpha = \frac{1}{b^2 - a^2} \ \beta = \frac{1}{a^2 - b^2} \end{array} \right. \end{eqnarray*} $$ 再推个几项,就发现规律比较明显,具体参考这里idea
那么有: $$ c_i = \frac{1}{\prod\limits_{j \neq i} (a_j^2 - a_i^2)} $$ 则: $$ \frac{1}{\prod\limits_{j \neq i} (a_j^2 - a_i^2)} = \sum \frac{c_i}{a_i^2 + x^2} $$ 而后转化成积分的和便可。spa
其实我更喜欢下面这种方法: 考虑: $$ \begin{eqnarray*} \frac{1}{\prod a_i^2 + x^2} = \sum \frac{c_i}{a_i^2 + x^2} \end{eqnarray*} $$ 那么移项有: $$ \begin{eqnarray*} 1 = \sum \frac{c_i \prod (a_j^2 + x^2)}{a_i^2 + x^2} \end{eqnarray*} $$ 那么显然右边的$\prod$项中有一项是$a_i^2 + x^2$,那么考虑拆出来: $$ \begin{eqnarray*} 1 = c_i \prod\limits_{j \neq i}(a_j^2 +x^2) + \sum\limits_{j \neq i} \frac{c_j \prod (a_k^2 + x^2)}{a_j^2 + x^2} \end{eqnarray*} $$ 那么咱们令$x = a_i \cdot i$,那么$x^2 = -a_i^2$,显然有: $$ \begin{eqnarray*} a_j^2 - a_i^2 &\neq& 0 \ \prod (a_k^2 - a_i^2) &=& 0 \end{eqnarray*} $$ 由于$k$是会等于$i$的。 那么就有: $$ \begin{eqnarray*} 1 = c_i \prod\limits_{j \neq i} (a_j^2 - a_i^2) + 0 \end{eqnarray*} $$ 因此: $$ \begin{eqnarray*} c_i = \frac{1}{\prod\limits_{j \neq i} (a_j^2 - a_i^2)} \end{eqnarray*} $$
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll P = (ll) 1e9 + 7; #define N 1010 ll qpow(ll x, ll n) { ll res = 1; while (n) { if (n & 1) { res = res * x % P; } x = x * x % P; n >>= 1; } return res; } int n; ll arr[N]; int main() { while (~scanf("%d", &n)) { for (int i = 1; i <= n; ++i) { scanf("%lld", arr + i); } ll ans = 0; for (int i = 1; i <= n; ++i) { ll tmp = 2ll * arr[i]; for (int j = 1; j <= n; ++j) { if (i == j) { continue; } tmp = tmp * (arr[j] * arr[j] % P - arr[i] * arr[i] % P + P) % P; } ans = (ans + qpow(tmp, P - 2)) % P; } printf("%lld\n", ans); } return 0; }
题意: 有一个$n$维实数域$\mathbb{R}$的向量$(a_1/m, a_2/m, \cdots, a_n/m)$,要求找到另外一个点$P = (p_1, p_2, \cdots, p_n)$知足如下要求:
而且要使得 $$ \begin{eqnarray*} ||A - P||2^2 = \sum\limits{i = 1}^n (a_i/m - p_i)^2 \end{eqnarray*} $$ 最小。
思路: 咱们改变一下限制条件,咱们将第三个条件改为: $$ \begin{eqnarray*} p_1 + p_2 + \cdots + p_n = m \end{eqnarray*} $$ 那么对于答案式子,能够做以下改变: $$ \begin{eqnarray*} \sum\limits_{i = 1}^n (a_i/m - p_i)^2 = \frac{1}{m^2} \sum\limits_{i = 1}^n (a_i - p_i)^2 \end{eqnarray*} $$ 咱们考虑要求的答案式子至关于求什么,假设全部$p_i = 0$(显然不符合条件,无论了,先这样),那么就至关于将$a_i$画在坐标轴上画成矩形,求矩形的高度的平方,以下图:
那么咱们加入$p_i$能有什么用? 显然加入$p_i$可以下降一些矩形的高度,感性理解一下,确定是尽可能把高的矩形下降高度。 那么咱们先降$A_1$,使得它的高度与$A_2$相同。 而后呢? 而后其实能够把$A_1, A_2$合并起来当作一个矩形,那继续作什么?固然是下降$A_1, A_2$这个合并体的高度了。 而后继续作,咱们发现有一步$m$不够了,那怎么办? 那确定是均摊给高度相同而且最高的那个合并体,假设已经用了$k$了,那么还剩下$m - k$,假设合并体的宽度(即个数)是$i$。 因此均摊给这其中每一个独立个体的高度贡献就是 $$ \frac{m - k}{i} $$ 而后这个合并体的最终高度就是 $$ a_i - \frac{m - k}{i} $$ 而后加上剩下$n - i$个未变更矩形的贡献便可。 注意贡献中咱们提取了一个$\frac{1}{m^2}$,不要忘记。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define N 10010 int n; ll a[N], m; struct frac{ ll x,y; frac() {} frac(ll x, ll y) : x(x), y(y) {} ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } frac operator+(const frac &u){ ll p, q; p = x * u.y + y * u.x; q = u.y * y; ll d = gcd(p, q); p /= d; q /= d; return (frac){p, q}; } frac operator-(const frac &u){ ll p, q; p = x * u.y - y * u.x; q = u.y * y; ll d = gcd(p, q); p /= d; q /= d; return (frac){p, q}; } frac operator*(const frac &u){ ll p, q; p = u.x * x; q = u.y * y; ll d = gcd(p, q); p /= d; q /= d; return (frac){p, q}; } frac operator/(const frac &u){ ll p, q; p = u.y * x; q = u.x * y; ll d = gcd(p,q); p /= d; q /= d; return (frac){p,q}; } void sqr() { *this = (*this) * (*this); } void print(){ y == 1 ? printf("%lld\n", x) : printf("%lld/%lld\n", x, y); } }; int main() { while (scanf("%d%lld", &n, &m) != EOF) { for (int i = 1; i <= n; ++i) scanf("%lld", a + i); sort(a + 1, a + 1 + n, [&](ll x, ll y){ return x > y; }); ll k = m; frac ans = frac(0, 1); for (int i = 1; i <= n; ++i) { if (i < n && (1ll * i * (a[i] - a[i + 1])) <= k) { k -= 1ll * i * (a[i] - a[i + 1]); } else { ans = ans + frac(1ll * (i * a[i] - k) * (i * a[i] - k), 1ll * i * m * m); for (int j = i + 1; j <= n; ++j) { ans = ans + frac(1ll * a[j] * a[j], m * m); } ans.print(); break; } } } return 0; }
题意: 有$n$个$m$元组$(v_1, v_2, \cdots, v_n)$其中$v_i = (a_{i, 1}, \cdots, a_{i, m})$,定义$count(x)$为: $$ \begin{eqnarray*} count(x) = \frac{1}{2^m} \sum\limits_{i = 1}^n \prod\limits_{j = 1}^m (1 - (-1)^{|a_{i, j} ; \wedge x|}) \end{eqnarray*} $$ 求$\oplus_{x = 0}^{2^k - 1} (count(x) \cdot 3^x \bmod 10^9 + 7)$
思路:
为啥作一遍$FWT_{xor}$就能够了呢,我也不太清楚。。 根据$qls$的说法,$count(x)$后面的$\prod$的项须要展开成$2^m$项,可是由于有这样一个性质: $$ |a; and ; x| + |b ; and ; x| = |(a;xor;b);and;x| (mod; 2) $$ 由于只考虑结果的奇偶性的话,$a, b$相同的位$and; x$后必定会贡献$0$。 能够推广成: $$ |a; and ; x| + |b ; and ; x| + |c ; and ; x| = |(a;xor;b;xor;c);and;x| (mod; 2) $$ 由于显然能够当作: $$ |(a; xor;b) ; and; x| + |c ;and; x| = |(a;xor;b;xor;c);and;x| (mod; 2) $$
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define N 2000010 const ll p = 1e9 + 7; int n, m, k; int f[N]; void DFS(vector <int> &vec, int i, int x, int p) { if (i < (int)vec.size()) { DFS(vec, i + 1, x, p); DFS(vec, i + 1, x ^ vec[i], -p); } else { f[x] += p; } } void FWT(int *x, int len) { for (int i = 2; i <= len; i <<= 1) { int step = i >> 1; for (int j = 0; j < len; j += i) { for (int k = j; k < j + step; ++k) { ll a = x[k], b = x[k + step]; x[k] = (a + b) % p; x[k + step] = (a - b + p) % p; } } } } ll qmod(ll base, ll n) { ll res = 1; while (n) { if (n & 1) { res = res * base % p; } base = base * base % p; n >>= 1; } return res; } int main() { while (scanf("%d%d%d", &n, &m, &k) != EOF) { for (int i = 0; i < 1 << k; ++i) f[i] = 0; for (int i = 1; i <= n; ++i) { vector <int> a(m); for (auto &it : a) scanf("%d", &it); DFS(a, 0, 0, 1); } FWT(f, 1 << k); ll res = 0; ll three = 1; ll inv = qmod(qmod(2, m), p - 2); for (int i = 0; i < 1 << k; ++i) { res ^= 1ll * f[i] * three % p * inv % p; three = three * 3 % p; } printf("%lld\n", res); } return 0; }
题意: 要求构造一个长度为$2(n + m)$的字符串,使得有一种子序列拆分拆分出$n$个$AB$以及$m$个$BA$。 问方案数。
思路: 考虑$f[x][y]$表示有$x$个$A$以及$y$个$B$的合法前缀的方案数,那么考虑推到$f[x + 1][y]$,首先要知足:
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define N 2050 const ll p = 1e9 + 7; int n, m; ll f[N][N]; void add(ll &x, ll y) { x += y; if (x >= p) x -= p; } int main() { while (scanf("%d%d", &n, &m) != EOF) { f[0][0] = 1; for (int i = 0; i <= n + m; ++i) { for (int j = 0; j <= n + m; ++j) { if ((i + 1) <= n || (i + 1 - n) <= j) { add(f[i + 1][j], f[i][j]); } if ((j + 1) <= m || (j + 1 - m) <= i) { add(f[i][j + 1], f[i][j]); } } } printf("%lld\n", f[n + m][n + m]); for (int i = 0; i <= n + m + 10; ++i) { for (int j = 0; j <= n + m + 10; ++j) { f[i][j] = 0; } } } return 0; }
题意: 给出三个点$A, B, C$,询问在三角形中随机选取一个点,而后会构成三个三角形$S_{PAB}, S_{PBC}, S_{PCA}$,问: $$ \begin{eqnarray*} E = max{S_{PAB}, S_{PBC}, S_{PCA}} \end{eqnarray*} $$
思路: 答案是$\displaystyle \frac{11}{2}$倍三角形的面积,,不知道为啥。
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; struct node { ll x, y; void input() { scanf("%lld %lld", &x, &y); } ll operator ^ (const node &other) const { return x * other.y - y * other.x; } node operator - (const node &other) const { return {x - other.x, y - other.y}; } }p[5]; int main() { while (~scanf("%lld %lld", &p[1].x, &p[1].y)) { p[2].input(); p[3].input(); ll ans = abs((p[1] - p[2]) ^ (p[1] - p[3])); ans *= 11; printf("%lld\n", ans); } return 0; }
题意: 有一个序列$a_i$,询问: $$ \begin{eqnarray*} \sum\limits_{S \subseteq A, \oplus_{x \in S} ;; x = 0} |S| \bmod 10^9 + 7 \end{eqnarray*} $$
思路: 考虑指望的线性性,咱们能够单独计算一个数在多少个集合中出现过。 首先咱们对整个序列求一个线性基$R$,那么剩下的$n - r$个数,每一个数出现的方案数是$2^{n - r - 1}$种。 由于考虑剩下的$n - r$个数,假设我先肯定谁必定要选,那么剩下的$n - r - 1$个数中我无论怎么取,基$R$中都有一种组合方式能组合出它们的异或和。 而且是惟一组合的,由于是基中是线性无关的。 再考虑$R$中的每个数的贡献。 首先咱们知道,咱们知道了一个序列$a_i$的线性基的大小是$r$,那么一个数要想有贡献,它的贡献一定是$2^{n - r - 1}$种。 那么有一种显然的作法就是,枚举基$R$中的每个数$R_i$,去掉这个数,剩下的数作线性基$B$,若是$R_i$还能插入到$B$中,说明$R_i$不属于线性基$B$构成的张成中,也就是说$B$中没有一种组合可以组合出$R_i$, 那么此时$R_i$的方案数为$0$,不然方案数就是$2^{n - r - 1}$。 有一种小优化是,先对剩下的$n - r$个数作一个线性基$B$,而后枚举$R_i$的时候,只要往$B$中插入$R_j(j \neq i)$便可。 由于这里数的大小是$64$位的,因此线性基的大小不会超过$64$ 时间复杂度:$\mathcal{O}(64n + 64^3)$
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define N 100010 #define M 65 const ll p = 1e9 + 7; int n; ll a[N]; int vis[N]; void add(ll &x, ll y) { x += y; if (x >= p) x -= p; } struct LB { ll d[M]; int id[M]; void init() { memset(d, 0, sizeof d); memset(id, -1, sizeof id); } bool insert(ll val, int _id = 1) { for (int i = 63; i >= 0; --i) { if (val & (1ll << i)) { if (!d[i]) { id[i] = _id; d[i] = val; break; } val ^= d[i]; } } return val > 0; } }A, B, BB; int main() { while (scanf("%d", &n) != EOF) { for (int i = 1; i <= n; ++i) vis[i] = 0; for (int i = 1; i <= n; ++i) { scanf("%lld", a + i); } ll res = 0; ll two = 5e8 + 4; A.init(); int nullity = 0; for (int i = 1; i <= n; ++i) { if (!A.insert(a[i], i)) { ++nullity; add(two, two); } } add(res, two * nullity % p); B.init(); for (int i = 0; i <= 63; ++i) { if (~A.id[i]) { vis[A.id[i]] = 1; } } vector <ll> vec; for (int i = 1; i <= n; ++i) { if (!vis[i]) { B.insert(a[i], i); } else { vec.push_back(a[i]); } } int sze = (int)vec.size(); for (int i = 0; i < sze; ++i) { BB = B; for (int j = 0; j < sze; ++j) { if (i != j) { BB.insert(vec[j]); } } if (!BB.insert(vec[i])) { add(res, two); } } printf("%lld\n", res); } return 0; }
题意: 在二维平面上有$n$个点$(x_i, y_i, a_i, b_i)$,要求将他们划分到集合$A$或者集合$B$中。 若是划分到集合$A$,那么这个点的贡献就是$a_i$,不然贡献就是$b_i$。 而且要知足不存在一个$(i, j)$使得$i \in A, j \in B, x_i \geq x_j, y_i \leq y_j$
思路: 考虑那个限制条件,那么在平面上必然存在一条非递减的折线,使得折线左上角的点都是集合$A$的,右下角的点都是集合$B$的。 那么咱们假设折线是贴着集合$B$上的点的,考虑$dp[i]$表示到了点$i$,它的左边的点的贡献是多少。 那么有一种转移方式,就是枚举以前的一个点$j$,使得$x_j \leq x_k \leq x_i$:
那么显然在转移过程当中,咱们先对$x$轴从小到大排序,再对$y$轴从大到小排序。 那么咱们转移的时候其实枚举$y_j$就能够了,由于相同的$y_j$而$x_j$不一样,后面的点对他们产生的贡献是同样的,因此直接他们取$max$而后后面加贡献就能够了。 为何要对$y$轴从大到小排序,由于折线是非递减的,咱们转移时要找的$y_j \leq y_i$,因此咱们强制让$y_i$大的先作,这样避免重复转移状态。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define INF 0x3f3f3f3f3f3f3f3f #define N 100010 int n, m; ll H[N]; struct node { ll x, y, a, b; node() {} void scan() { scanf("%lld%lld%lld%lld", &x, &y, &a, &b); H[++m] = y; } bool operator < (const node &other) const { if (x != other.x) return x < other.x; return y > other.y; } }a[N]; struct SEG { struct node { ll Max, lazy; node() { Max = lazy = 0; } void add(ll x) { Max += x; lazy += x; } node operator + (const node &other) const { node res = node(); res.Max = max(Max, other.Max); return res; } }t[N << 2]; void build(int id, int l, int r) { t[id] = node(); if (l == r) return; int mid = (l + r) >> 1; build(id << 1, l, mid); build(id << 1 | 1, mid + 1, r); } void pushdown(int id) { ll &lazy = t[id].lazy; if (!lazy) return; t[id << 1].add(lazy); t[id << 1 | 1].add(lazy); lazy = 0; } void update(int id, int l, int r, int ql, int qr, ll x) { if (l >= ql && r <= qr) { t[id].add(x); return; } int mid = (l + r) >> 1; pushdown(id); if (ql <= mid) update(id << 1, l, mid, ql, qr, x); if (qr > mid) update(id << 1 | 1, mid + 1, r, ql, qr, x); t[id] = t[id << 1] + t[id << 1 | 1]; } void update2(int id, int l, int r, int pos, ll x) { if (l == r) { t[id].Max = max(t[id].Max, x); return; } int mid = (l + r) >> 1; pushdown(id); if (pos <= mid) update2(id << 1, l, mid, pos, x); else update2(id << 1 | 1, mid + 1, r, pos, x); t[id] = t[id << 1] + t[id << 1 | 1]; } ll query(int id, int l, int r, int ql, int qr) { if (l >= ql && r <= qr) { return t[id].Max; } int mid = (l + r) >> 1; pushdown(id); ll res = 0; if (ql <= mid) res = max(res, query(id << 1, l, mid, ql, qr)); if (qr > mid) res = max(res, query(id << 1 | 1, mid + 1, r, ql, qr)); return res; } }seg; int main() { while (scanf("%d", &n) != EOF) { m = 0; H[++m] = -INF; H[++m] = INF; for (int i = 1; i <= n; ++i) { a[i].scan(); } sort(a + 1, a + 1 + n); sort(H + 1, H + 1 + m); m = unique(H + 1, H + 1 + m) - H - 1; for (int i = 1; i <= n; ++i) { a[i].y = lower_bound(H + 1, H + 1 + m, a[i].y) - H; } seg.build(1, 1, m); for (int i = 1; i <= n; ++i) { ll g = seg.query(1, 1, m, 1, a[i].y); seg.update2(1, 1, m, a[i].y, g + a[i].b); seg.update(1, 1, m, a[i].y + 1, m, a[i].b); seg.update(1, 1, m, 1, a[i].y - 1, a[i].a); } printf("%lld\n", seg.t[1].Max); } return 0; }
题意: 判断$\displaystyle \frac{x}{a}$与$\displaystyle \frac{y}{b}$的大小关系。
思路:
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long ll x, y, a, b; void out(ll A, ll B) { if (A == B) { puts("="); } else if (A < B) { puts("<"); } else { puts(">"); } } int main() { while (scanf("%lld%lld%lld%lld", &x, &a, &y, &b) != EOF) { ll A = x / a; x %= a; ll B = y / b; y %= b; if (A != B) { out(A, B); } else { out(x * b, y * a); } } return 0; }