[LibreOJ 2182][SDOI2015]寻宝游戏
很多人说事动态虚树……其实也不算是吧,虽然这个东西用力虚树的思路惹。
第一个想到的思路……就事动态的维护虚树,然后虚树上所有边的长度乘二就事答案。然后我们考虑一点……虚树求的时候需要对DFS序(严格来说事欧拉序)排序,所以我们大致可以认为,虚树上做欧拉回路的本质就事按照DFS序扫描,换言之就事DFS一遍,然后进入和回溯事都把边记录到答案里,这个值也就事虚树上按DFS序排序之后两两相邻点的距离的和(首尾也要额外算一遍)。
然后这样一来,我们发现虚树上的非关键点就可以删掉了。因为非关键点也就是所有两两在DFS序中相邻的关键点的LCA,然后那两个关键点的距离也就等于他们到这个非关键点的距离的和。因此我们不需要维护非关键点,问题就简单了许多……
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <vector> #include <utility> #include <set> const int maxn = 100005; using ll = long long; using pii = std::pair<int, ll>; std::vector<pii> G[maxn]; void add_edge(int u, int v, ll w) { G[u].push_back(pii(v, w)); } void ins_edge(int u, int v, ll w) { add_edge(u, v, w); add_edge(v, u, w); } int anc[maxn][17]; int dep[maxn], dfn[maxn]; ll dis[maxn]; int dcnt = 0; void dfs(int x, int fa = -1) { anc[x][0] = fa; dfn[x] = ++ dcnt; for(auto &e : G[x]) { int v = e.first; ll w = e.second; if(v != fa) { dep[v] = dep[x] + 1; dis[v] = dis[x] + w; dfs(v, x); } } } int n; void process() { memset(anc, -1, sizeof(anc)); dfs(1); for(int j = 1; (1 << j) < n; j ++) { for(int i = 1; i <= n; i ++) { int a = anc[i][j - 1]; if(a != -1) { anc[i][j] = anc[a][j - 1]; } } } } int lca(int u, int v) { if(dep[u] < dep[v]) std::swap(u, v); for(int j = 16; j >= 0; j --) { int a = anc[u][j]; if(a != -1 && dep[a] >= dep[v]) { u = a; } } if(u == v) return u; for(int j = 16; j >= 0; j --) { int a1 = anc[u][j], a2 = anc[v][j]; if(a1 != -1 && a2 != -1 && a1 != a2) { u = a1; v = a2; } } return anc[u][0]; } ll calc_dist(int u, int v) { return dis[u] + dis[v] - 2LL * dis[lca(u, v)]; } // template <typename T> struct Comp { bool operator ()(const int &a, const int &b) const { return dfn[a] < dfn[b]; } }; ll now = 0LL; std::set<int, Comp> S; bool sta[maxn]; void add(int p) { auto suc = S.upper_bound(p); if(suc != S.begin()) { auto pre = -- suc; ++ suc; now += calc_dist(*pre, p); if(suc != S.end()) { now -= calc_dist(*pre, *suc); } } if(suc != S.end()) { now += calc_dist(*suc, p); } S.insert(p); } void del(int p) { auto suc = S.upper_bound(p); if(suc != S.end()) { now -= calc_dist(*suc, p); } if((*S.begin()) != p) { -- suc; -- suc; int prev = *suc; now -= calc_dist(prev, p); ++ suc; ++ suc; if(suc != S.end()) { now += calc_dist(prev, *suc); } } S.erase(p); } ll query() { if(S.size() <= 1) return 0LL; ll ret = now; auto las = S.end(); -- las; ret += calc_dist(*S.begin(), *las); return ret; } int main() { int m; scanf("%d%d", &n, &m); for(int i = 1; i <= n - 1; i ++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); ins_edge(u, v, w); } process(); while(m --) { int x; scanf("%d", &x); if(sta[x]) { del(x); } else { add(x); } sta[x] = !sta[x]; printf("%lld\n", query()); } return 0; }
[LibreOJ 6436][PKUSC2018]神仙的游戏
考场上写炸了FFT的zzs事野蛮人(确信)
兄啊这题怎么还卡常啊!
不妨设\(n = |S|\)。那么如果串有一个长度为$i$的border,就意味着串有一个长度为\(n - i\)的循环节。
那么考虑有两个非?的位置\(i\)和\(j\)(不妨设\(i < j\)),且\(S_i\neq S_j\)。那么我们设\(l = j - i\),则\(l\)的约数都不能成为循环节的长度,否则会出现矛盾(一个位置又要是0又要是1)。
那么考虑对于所有可能的\(l\)判定是否存在这种不合法的\(i\)与\(j\)。我们利用编辑距离函数:
\[f_l = \sum_{i} A_i A_{i + l}(A_i - A_{i + l})^2\]
这里\(A_i\)相当于\(S_i\)的一个重编码,\(S_i\)为?时应当为0,其他情况一种为1一种为2。这个函数不方便我们卷积,所以我们考虑把\(A\)翻转一下(新的序列称为\(B\)):
\[
\begin{aligned}
f_l &= \sum_{i} B_{n - i - 1} A_{i + l}(B_{n - i - 1} A_{i + l})^2\\
&= \sum_{i} B_{n - i - 1}^3 A_{i + l} - 2B_{n - i - 1}^2 A_{i + l}^2 + B_{n - i - 1}A_{i + l}^3
\end{aligned}\]
然后问题变成了三个卷积。FFT一下就行了(请注意IDFT只需要一次……否则会被卡常)。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <utility> #include <cmath> #include <complex> #include <vector> using R = double; struct C { R x, y; C(R xx = 0.00, R yy = 0.00) { x = xx; y = yy; } R real() { return x; } }; C operator +(const C &a, const C &b) { return C(a.x + b.x, a.y + b.y); } C operator -(const C &a, const C &b) { return C(a.x - b.x, a.y - b.y); } C operator *(const C &a, const C &b) { return C(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); } using ll = long long; constexpr R pi = acos(-1); int flip(int bi, int x) { int ret = 0; for(int i = 0; i < bi; i ++) { if(x & (1 << i)) { ret += (1 << (bi - i - 1)); } } return ret; } void FFT(C *A, int bi, R flag = 1.00) { int n = 1 << bi; for(int i = 0; i < n; i ++) { int v = flip(bi, i); if(i < v) std::swap(A[v], A[i]); } for(int L = 1; L < n; L <<= 1) { R rd = pi / (R(L)); C xi_n(cos(rd), sin(flag * rd)); for(int j = 0; j < n; j += (L << 1)) { C xi(1.00, 0.00); for(int i = j; i < j + L; i ++) { C x = A[i], y = A[i + L]; A[i] = x + xi * y; A[i + L] = x - xi * y; xi = xi * xi_n; } } } } const int maxn = 500005; R tot[maxn]; C T[maxn << 2], TT[maxn << 2], D[maxn << 2]; char S[maxn]; inline int conv(char c) { if(c == '1') return 2; if(c == '0') return 1; return 0; } int A[maxn], B[maxn]; bool boom[maxn]; int main() { scanf("%s", S); int n = strlen(S); for(int i = 0; i < n; i ++) { A[i] = B[n - 1 - i] = conv(S[i]); } int len = 1, bi = 0; while(len < (2 * n)) { len <<= 1; bi ++; } C z(0.00, 0.00); // std::fill(T, T + len, z); // std::fill(TT, TT + len, z); for(int i = 0; i < n; i ++) { T[i] = A[i] * A[i] * A[i]; TT[i] = B[i]; } FFT(T, bi); FFT(TT, bi); for(int i = 0; i < len; i ++) { D[i] = D[i] + (T[i] * TT[i]); } std::fill(T, T + len, z); std::fill(TT, TT + len, z); for(int i = 0; i < n; i ++) { T[i] = A[i] * A[i]; TT[i] = B[i] * B[i]; } FFT(T, bi); FFT(TT, bi); for(int i = 0; i < len; i ++) { D[i] = D[i] + C(-2.00, 0.00) * (T[i] * TT[i]); } std::fill(T, T + len, z); std::fill(TT, TT + len, z); for(int i = 0; i < n; i ++) { T[i] = A[i]; TT[i] = B[i] * B[i] * B[i]; } FFT(T, bi); FFT(TT, bi); for(int i = 0; i < len; i ++) { D[i] = D[i] + (T[i] * TT[i]); } FFT(D, bi, -1.00); for(int i = 1; i < n; i ++) { if(fabs(D[n - 1 + i].real() / (R(len))) > 0.99) { boom[i] = true; } } for(int i = 1; i <= n; i ++) { for(int j = i * 2; j <= n; j += i) { boom[i] = (boom[i] || boom[j]); } } ll ret = 0LL; for(int i = 1; i <= n; i ++) { if(!boom[n - i]) { ret ^= (ll(i)) * (ll(i)); } } printf("%lld\n", ret); return 0; }
[LibreOJ 2562][SDOI2018]战略游戏
aji圆方树毁我青春,,,省选现场看出来是圆方树但写不出点双滚粗。
显然可以割的点是某两个点的某一条简单路径上的割点,然后两点间的简单路劲的并就是圆方树上的路径,割点在圆方树上就是非叶子的圆点。我们求所有两点路径的并,就需要虚树。
然后做完了……直接建圆方树,每次询问建虚树xjb统计即可。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <utility> #include <vector> #include <stack> #include <queue> #include <map> #include <set> const int maxn = 100005; const int maxm = 200005; std::vector<int> G[maxn]; void cz1() { for(int i = 0; i < maxn; i ++) { G[i].clear(); } } void add_edge1(int u, int v) { G[u].push_back(v); G[v].push_back(u); } using pii = std::pair<int, int>; int dcnt, bcc_cnt; int pre[maxn]; bool iscut[maxn]; int bccno[maxn]; std::vector<int> bcc[maxm]; int dfs(int x, int fa = -1) { static std::stack<pii> S; pre[x] = ++ dcnt; int low = pre[x]; int cld = 0; for(auto v : G[x]) { pii e = std::make_pair(x, v); if(!pre[v]) { S.push(e); cld ++; int lowv = dfs(v, x); low = std::min(low, lowv); if(lowv >= pre[x]) { iscut[x] = true; bcc_cnt ++; bcc[bcc_cnt].clear(); while(true) { pii ee = S.top(); S.pop(); int f = ee.first, g = ee.second; if(bccno[f] != bcc_cnt) { bccno[f] = bcc_cnt; bcc[bcc_cnt].push_back(f); } if(bccno[g] != bcc_cnt) { bccno[g] = bcc_cnt; bcc[bcc_cnt].push_back(g); } if(f == x && g == v) break; } } } else if(pre[v] < pre[x] && v != fa) { S.push(e); low = std::min(low, pre[v]); } } return low; } int n, m; void calc_bcc() { std::fill(pre, pre + 1 + n, 0); std::fill(iscut, iscut + 1 + n, false); std::fill(bccno, bccno + 1 + m, 0); dcnt = bcc_cnt = 0; for(int i = 1; i <= n; i ++) { if(!pre[i]) dfs(i); } } std::vector<int> G2[maxn + maxm]; void cz2() { for(int i = 0; i < maxn + maxm; i ++) G2[i].clear(); } void add_edge2(int u, int v) { G2[u].push_back(v); G2[v].push_back(u); } int anc[maxn + maxm][19], dep[maxn + maxm], d[maxn + maxm]; int dfn[maxn + maxm], siz[maxn + maxm]; int cnt2; void dfs_tree(int x, int fa = -1, int depth = 0, int s = 0) { #ifdef LOCAL printf("V (%d, %d, %d, %d)\n", x, fa, depth, s); #endif cnt2 ++; dfn[x] = cnt2; siz[x] = 1; d[x] = s; anc[x][0] = fa; dep[x] = depth; for(auto v : G2[x]) { if(v != fa) { dfs_tree(v, x, depth + 1, s + ((v <= n) ? 1 : 0)); siz[x] += siz[v]; } } } void process_lca() { memset(anc, -1, sizeof(anc)); cnt2 = 0; int s = bcc_cnt + n; dfs_tree(n + 1); #ifdef LOCAL printf("s : %d\n", s); #endif for(int j = 1; (1 << j) < s; j ++) { for(int i = 1; i <= s; i ++) { int a = anc[i][j - 1]; if(a != -1) anc[i][j] = anc[a][j - 1]; } } } int lca(int x, int y) { if(dep[x] < dep[y]) std::swap(x, y); for(int j = 18; j >= 0; j --) { int a = anc[x][j]; if(a != -1 && dep[a] >= dep[y]) { x = a; } } if(x == y) return x; for(int j = 18; j >= 0; j --) { int a1 = anc[x][j], a2 = anc[y][j]; if(a1 != -1 && a2 != -1 && a1 != a2) { x = a1; y = a2; } } return anc[x][0]; } void process() { calc_bcc(); cz2(); for(int i = 1; i <= bcc_cnt; i ++) { int id = n + i; for(auto u : bcc[i]) { add_edge2(u, id); } } process_lca(); } int get_delta(int x, int y) { // y is x's ancestor. return (d[anc[x][0]] - d[y]); } bool is_anc(int fa, int bef) { int l1 = dfn[fa], r1 = dfn[fa] + siz[fa] - 1; int l2 = dfn[bef], r2 = dfn[bef] + siz[bef] - 1; return (l1 <= l2 && r2 <= r1); } int query(int sz) { std::vector<int> V; for(int i = 1; i <= sz; i ++) { int x; scanf("%d", &x); V.push_back(x); } std::sort(V.begin(), V.end(), [&](const int &i, const int &j) { return dfn[i] < dfn[j]; }); std::set<int> ds; ds.insert(V[0]); for(int i = 1; i < sz; i ++) { ds.insert(V[i]); ds.insert(lca(V[i - 1], V[i])); } V.clear(); for(auto u : ds) V.push_back(u); std::sort(V.begin(), V.end(), [&](const int &i, const int &j) { return dfn[i] < dfn[j]; }); std::stack<int> S; int ans = 0; for(int i = 0; i < V.size(); i ++) { int u = V[i]; while(!S.empty() && !is_anc(S.top(), u)) { S.pop(); } if(!S.empty()) { #ifdef LOCAL printf("ans (%d, %d) : %d\n", u, S.top(), get_delta(u, S.top())); #endif ans += get_delta(u, S.top()); } S.push(u); } for(auto u : ds) { if(u <= n) ans ++; } ans -= sz; return ans; } int main() { int T; scanf("%d", &T); while(T --) { scanf("%d%d", &n, &m); cz1(); for(int i = 1; i <= m; i ++) { int u, v; scanf("%d%d", &u, &v); add_edge1(u, v); } process(); int q; scanf("%d", &q); while(q --) { int sz; scanf("%d", &sz); printf("%d\n", query(sz)); #ifdef LOCAL fflush(stdout); #endif } } return 0; }
[LibreOJ 121]可离线动态图连通性
LCT+扫描线应该随便做吧这题……但我学了一下线段树分治
这个问题有删除非常的恶心,让我们考虑怎么去掉删除的影响。
每条边存在的时间段是一个区间,而区间在线段树上可以被表示为\(O(\log n)\)个区间。然后我们以时间为下标,对所有询问建线段树,然后对一段区间加边就是一个区间打标记,最后扫一遍线段树就可以解决问题。
同时需要注意,这个题在DFS线段树的过程中,往父亲回溯的时候是需要撤销之前的操作的。这样的话我们的并查集就不能使用路径压缩,但是可以按轶合并。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <utility> #include <vector> #include <stack> #include <queue> #include <map> const int maxn = 5005; const int maxm = 500005; using pii = std::pair<int, int>; struct Node { Node *fa; int siz; Node() { fa = NULL; siz = 1; } void sc(Node *c) { siz += c -> siz; c -> fa = this; } void brk() { fa -> siz -= siz; fa = NULL; } }; Node *r[maxn]; int n; void init_set() { for(int i = 1; i <= n; i ++) { r[i] = new Node(); } } Node *get_fa(Node *x) { if(x -> fa == NULL) { return x; } else { return get_fa(x -> fa); } } Node *link_set(Node *x, Node *y) { if(x -> siz < y -> siz) std::swap(x, y); x -> sc(y); return y; } Node *merge_set(Node *x, Node *y) { return link_set(get_fa(x), get_fa(y)); } bool is_same(Node *x, Node *y) { return (get_fa(x) == get_fa(y)); } const int maxno = maxm << 2; std::vector<pii> event[maxno]; void add_event(int o, int L, int R, int ql, int qr, const pii &e) { if(ql <= L && R <= qr) { event[o].push_back(e); } else { int M = (L + R) / 2; if(ql <= M) add_event(o << 1, L, M, ql, qr, e); if(qr > M) add_event(o << 1 | 1, M + 1, R, ql, qr, e); } } pii que[maxno]; void add_query(int o, int L, int R, int p, const pii &e) { if(L == R) { que[o] = e; } else { int M = (L + R) / 2; if(p <= M) { add_query(o << 1, L, M, p, e); } else { add_query(o << 1 | 1, M + 1, R, p, e); } } } int ret[maxno]; std::stack<std::pair<Node*, int> > S; void solve(int o, int L, int R) { for(auto e : event[o]) { int u = e.first, v = e.second; if(!is_same(r[u], r[v])) { #ifdef LOCAL printf("Merging %d and %d.\n", u, v); #endif S.push(std::make_pair(merge_set(r[u], r[v]), o)); } } if(L == R) { int u = que[o].first, v = que[o].second; if(u == -1 && v == -1) { ret[L] = -1; } else { if(is_same(r[u], r[v])) { ret[L] = 1; } else { ret[L] = 0; } } } else { int M = (L + R) / 2; solve(o << 1, L, M); solve(o << 1 | 1, M + 1, R); } while(!S.empty() && S.top().second >= o) { Node *u = S.top().first; S.pop(); u -> brk(); } } std::map<pii, std::stack<int> > ma; std::vector<pii> V; int main() { int m; scanf("%d%d", &n, &m); init_set(); pii fl(-1, -1); for(int i = 1; i <= m; i ++) { pii e; int op; scanf("%d%d%d", &op, &e.first, &e.second); if(e.first > e.second) { std::swap(e.first, e.second); } if(op == 2) { add_query(1, 1, m, i, e); } else { add_query(1, 1, m, i, fl); V.push_back(e); if(op == 0) { ma[e].push(i); } else { int last = ma[e].top(); ma[e].pop(); add_event(1, 1, m, last, i - 1, e); } } } for(auto e : V) { while(!ma[e].empty()) { int g = ma[e].top(); ma[e].pop(); add_event(1, 1, m, g, m, e); } } solve(1, 1, m); for(int i = 1; i <= m; i ++) { if(ret[i] != -1) { if(ret[i]) { puts("Y"); } else { puts("N"); } } } return 0; }
[LibreOJ 2558][LNOI2014]LCA
现在才做这题TAT
如果询问的是一个子集和\(z\)的所有LCA的深度的和,那可以把子集里每一个元素到根的路径全部加1,然后根到\(z\)的路径上的和就是答案。
如果是区间的话,每次都扫一次整个区间一定会T……所以考虑把区间拆成两个前缀区间,然后离线,给询问排个序然后就好做了。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <utility> #include <vector> const int maxn = 50005; using ll = long long; const ll ha = 201314LL; std::vector<int> G[maxn]; void add_edge(int u, int v) { G[u].push_back(v); G[v].push_back(u); } const int maxno = maxn << 2; ll sumv[maxno], addv[maxno]; void maintain(int o) { sumv[o] = (sumv[o << 1] + sumv[o << 1 | 1]) % ha; } void paint(int o, int L, int R, ll v) { v %= ha; addv[o] += v; addv[o] %= ha; sumv[o] += (v * (ll(R - L + 1))) % ha; sumv[o] %= ha; } void pushdown(int o, int L, int R) { if(addv[o] != 0LL) { ll v = addv[o]; addv[o] = 0; int M = (L + R) / 2; int lc = o << 1, rc = o << 1 | 1; paint(lc, L, M, v); paint(rc, M + 1, R, v); } } int ql, qr; ll v; void modify(int o, int L, int R) { if(ql <= L && R <= qr) { paint(o, L, R, v); } else { pushdown(o, L, R); int M = (L + R) / 2; if(ql <= M) modify(o << 1, L, M); if(qr > M) modify(o << 1 | 1, M + 1, R); maintain(o); } } ll query(int o, int L, int R) { if(ql <= L && R <= qr) { return sumv[o]; } else { pushdown(o, L, R); int M = (L + R) / 2; ll ans = 0; if(ql <= M) ans = (ans + query(o << 1, L, M)) % ha; if(qr > M) ans = (ans + query(o << 1 | 1, M + 1, R)) % ha; return ans; } } int siz[maxn], fa[maxn], dep[maxn], hson[maxn]; void dfs_1(int x, int f = 0, int depth = 0) { fa[x] = f; dep[x] = depth; siz[x] = 1; int maxs = 0; for(auto v : G[x]) { if(v != f) { dfs_1(v, x, depth + 1); siz[x] += siz[v]; if(siz[v] > maxs) { maxs = siz[v]; hson[x] = v; } } } } int top[maxn], tid[maxn], dfn[maxn]; void dfs_2(int x, int a) { static int cnt = 0; cnt ++; top[x] = a; tid[cnt] = x; dfn[x] = cnt; if(hson[x]) { dfs_2(hson[x], a); } else { return; } for(auto v : G[x]) { if(v != fa[x] && v != hson[x]) { dfs_2(v, v); } } } int n; void update(int x, int y, const ll &delta) { if(top[x] == top[y]) { if(dfn[x] > dfn[y]) std::swap(x, y); ql = dfn[x], qr = dfn[y]; v = delta; modify(1, 1, n); return; } if(dep[top[x]] < dep[top[y]]) std::swap(x, y); ql = dfn[top[x]], qr = dfn[x]; v = delta; modify(1, 1, n); update(fa[top[x]], y, delta); } ll query(int x, int y) { if(top[x] == top[y]) { if(dfn[x] > dfn[y]) std::swap(x, y); ql = dfn[x], qr = dfn[y]; return query(1, 1, n); } if(dep[top[x]] < dep[top[y]]) std::swap(x, y); ql = dfn[top[x]], qr = dfn[x]; ll ret = query(1, 1, n); return (ret + query(fa[top[x]], y)) % ha; } struct Q { int l, z; int id; ll p; bool operator <(const Q &res) const { if(l == res.l) { return id < res.id; } else { return l < res.l; } } }; Q que[maxn << 1]; ll ans[maxn]; int main() { int q; scanf("%d%d", &n, &q); for(int i = 2; i <= n; i ++) { int f; scanf("%d", &f); f ++; add_edge(f, i); } dfs_1(1); dfs_2(1, 1); for(int i = 1; i <= q; i ++) { int l, r, z; scanf("%d%d%d", &l, &r, &z); l ++; r ++; z ++; Q &L = que[i * 2 - 1]; Q &R = que[i * 2]; L.id = R.id = i; L.p = -1; R.p = 1; L.z = R.z = z; L.l = l - 1; R.l = r; } std::sort(que + 1, que + 1 + 2 * q); int p = 0; for(int i = 1; i <= 2 * q; i ++) { const Q &t = que[i]; while(p < t.l) { p ++; update(1, p, 1); } ans[t.id] = (ans[t.id] + t.p * query(1, t.z) + ha) % ha; } for(int i = 1; i <= q; i ++) { printf("%lld\n", ans[i]); } return 0; }
[LibreOJ 2137][ZJOI2015]诸神眷顾的幻想乡
复习SAM了呢~
那个度数为1的点至多有20个的条件非常神奇……让我们想想怎么用。
我们发现,(钦定根后)在树上有一条路径是弯的这种情况非常不好统计,但如果是直着下来就很好说(把所有从根到一个点的路径扔到SAM里,然后是经典题)。但是,任何一条路一定会在某个度数为1的点为根的情况下是直的(可以意会一下吧(逃
然后我们从那20个点每个点当根DFS一遍,把搞出来的每一条从根开始的路径放前缀Trie里。然后对前缀Trie构一个SAM就行啦~
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <queue> #include <vector> const int BUFSIZE = 128 * 1024 * 1024; char buf[BUFSIZE]; void *alloc(size_t size) { static char *cur = buf; if(cur - buf + size > BUFSIZE) { return malloc(size); } else { char *ret = cur; cur += size; return ret; } } const int maxn = 100005; std::vector<int> G[maxn]; int deg[maxn]; int ssiz; struct TNode { TNode *ch[10]; }; TNode *alloc_tn() { auto ret = (TNode*)alloc(sizeof(TNode)); memset(ret -> ch, 0, sizeof(ret -> ch)); return ret; } TNode *step(TNode *o, int c) { if(!(o -> ch[c])) { o -> ch[c] = alloc_tn(); } return (o -> ch[c]); } TNode *trt; int col[maxn]; void dfs(int x, int fa, TNode *last) { TNode *np = step(last, col[x]); for(auto v : G[x]) { if(v != fa) { dfs(v, x, np); } } } struct Node { int len; Node *fa; Node *ch[10]; }; std::vector<Node*> pool; Node *alloc_node(int len = 0, Node *fa = NULL) { Node *ret = (Node*)alloc(sizeof(Node)); ret -> len = len; ret -> fa = fa; memset(ret -> ch, 0, sizeof(ret -> ch)); pool.push_back(ret); return ret; } Node *rt; Node *extend(int c, Node *last) { Node *np = alloc_node(last -> len + 1); Node *p = last; while(p != NULL && p -> ch[c] == NULL) { p -> ch[c] = np; p = p -> fa; } if(p == NULL) { np -> fa = rt; } else { Node *q = p -> ch[c]; if(q -> len == p -> len + 1) { np -> fa = q; } else { Node *nq = alloc_node(p -> len + 1, q -> fa); memcpy(nq -> ch, q -> ch, sizeof(q -> ch)); q -> fa = np -> fa = nq; while(p != NULL && p -> ch[c] == q) { p -> ch[c] = nq; p = p -> fa; } } } return np; } void dfs_2(TNode *o, Node *last) { for(int i = 0; i < ssiz; i ++) { TNode *v = o -> ch[i]; if(!v) continue; Node *np = extend(i, last); dfs_2(v, np); } } using ll = long long; int main() { int n; scanf("%d%d", &n, &ssiz); for(int i = 1; i <= n; i ++) { scanf("%d", &col[i]); } trt = alloc_tn(); for(int i = 1; i <= n - 1; i ++) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); deg[u] ++; deg[v] ++; } for(int i = 1; i <= n; i ++) { if(deg[i] == 1) { dfs(i, 0, trt); } } rt = alloc_node(); dfs_2(trt, rt); ll ans = 0; for(auto p : pool) { if(p -> fa) { ans += p -> len - p -> fa -> len; } } printf("%lld\n", ans); return 0; }
[LibreOJ 2034][SDOI2016]排列计数
我这种池沼只会做水题陶冶身心了……
考虑用\(f_i\)表示长为\(i\)的完全错位全排列的个数,那么有\(i\)个错位的长度为\(n\)的全排列的数量就是\(\binom{n}{i} f_i\)。从这一点出发,可以得到一个式子(枚举错位的有几个):
\[n! = \sum_{i = 0}^n \binom{n}{i} f_i\]
考虑使用二项式反演,得到:
\[
\begin{aligned}
f_n &= \sum_{i = 0}^n (-1)^{n - i}\binom{n}{i} i!\\
&= \sum_{i = 0}^n (-1)^{n - i}\frac{n!}{(n - i)!}\\
&= n!\sum_{i = 0}^n \frac{(-1)^{n - i}}{(n - i)!}\\
&= n!\sum_{i = 0}^n \frac{(-1)^i}{i!}
\end{aligned}
\]
后面的和式可以预处理,然后就做完啦~
代码:
#include <cstdio> #include <cstring> using ll = long long; const ll ha = 1000000007LL; inline ll pow_mod(const ll &a, ll b) { ll ans = 1LL, res = a; while(b) { if(1LL & b) ans = (ans * res) % ha; res = (res * res) % ha; b >>= 1; } return ans; } inline ll inv(ll x) { return pow_mod(x, ha - 2LL); } const int N = 1000000; const int maxn = N + 5; ll fac[maxn], f[maxn]; inline void process() { fac[0] = 1LL; for(int i = 1; i <= N; i ++) { fac[i] = (fac[i - 1] * (ll(i))) % ha; } for(int i = 0; i <= N; i ++) { f[i] = (i % 2 == 1) ? (ha - 1LL) : 1LL; f[i] = (f[i] * inv(fac[i])) % ha; if(i > 0) f[i] = (f[i - 1] + f[i]) % ha; } } int main() { process(); int T; scanf("%d", &T); while(T --) { int n, m; scanf("%d%d", &n, &m); int p = n - m; ll bs = (fac[p] * f[p]) % ha; ll C = (fac[n] * inv((fac[p] * fac[m]) % ha)) % ha; printf("%lld\n", (bs * C) % ha); } return 0; }
[LibreOJ 6024]XLkxc
拉格朗日插值的模板题qwq
考虑\(f(n) = \sum_{i = 1}^n i^k\),这显然是一个\(k + 1\)次多项式(通过差分之类的手段可以证明),然后我们发现\(g(n) = \sum_{i = 1}^n f(n)\)可以用类似手段证明是\(k + 2\)次多项式。类似的,我们会发现,答案是一个\(k + 3\)次多项式。
分别对\(g\)以及答案插值即可。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <cctype> #include <algorithm> #include <utility> #include <queue> #include <climits> typedef long long ll; const ll ha = 1234567891LL; int k; ll a, n, d; ll pf[205], pg[205], fac[205]; ll pow_mod(const ll &a, ll b) { if(!b) return 1LL; ll ans = pow_mod(a, b >> 1); ans = (ans * ans) % ha; if(b & 1LL) ans = (ans * a) % ha; return ans; } ll inv(ll v) { return pow_mod(v, ha - 2LL); } void process() { fac[0] = 1LL; for(int i = 1; i <= k + 5; i ++) { fac[i] = (fac[i - 1] * inv(i)) % ha; #ifdef LOCAL printf("fac[%d] : %lld\n", i, fac[i]); #endif } for(int i = 1; i <= k + 3; i ++) { pf[i] = (pf[i - 1] + pow_mod(i, k)) % ha; #ifdef LOCAL printf("pf[%d] : %lld\n", i, pf[i]); #endif } for(int i = 1; i <= k + 3; i ++) { pg[i] = (pg[i - 1] + pf[i]) % ha; #ifdef LOCAL printf("pg[%d] : %lld\n", i, pg[i]); #endif } #ifdef LOCAL puts("Processing finished!"); #endif } ll g(ll x) { static ll sim_f[205], sim_g[205]; sim_f[0] = 1LL; for(int i = 1; i <= k + 3; i ++) { sim_f[i] = (x - (ll(i)) + ha) % ha; sim_f[i] = (sim_f[i] * sim_f[i - 1]) % ha; } sim_g[k + 4] = 1LL; for(int i = k + 3; i >= 1; i --) { sim_g[i] = (x - (ll(i)) + ha) % ha; sim_g[i] = (sim_g[i] * sim_g[i + 1]) % ha; } ll ret = 0; for(int i = 1; i <= k + 3; i ++) { ll ans = (((pg[i] * sim_f[i - 1]) % ha) * sim_g[i + 1]) % ha; ans = (ans * fac[i - 1]) % ha; ans = (ans * fac[k + 3 - i]) % ha; if(1 & (k + 3 - i)) { ret = (ret - ans + ha) % ha; } else { ret = (ret + ans) % ha; } } #ifdef LOCAL printf("g(%lld) : %lld\n", x, ret); #endif return ret; } ll h(ll x) { static ll ph[205]; ph[0] = g(a); for(int i = 1; i <= k + 4; i ++) { ph[i] = (ph[i - 1] + g(a + (ll(i)) * d)) % ha; } static ll sim_f[205], sim_g[205]; sim_f[0] = 1LL; for(int i = 1; i <= k + 4; i ++) { sim_f[i] = (x - (ll(i)) + ha) % ha; sim_f[i] = (sim_f[i] * sim_f[i - 1]) % ha; } sim_g[k + 5] = 1LL; for(int i = k + 4; i >= 1; i --) { sim_g[i] = (x - (ll(i)) + ha) % ha; sim_g[i] = (sim_g[i] * sim_g[i + 1]) % ha; } ll ret = 0; for(int i = 1; i <= k + 4; i ++) { ll ans = (((ph[i] * sim_f[i - 1]) % ha) * sim_g[i + 1]) % ha; ans = (ans * fac[i - 1]) % ha; ans = (ans * fac[k + 4 - i]) % ha; if(1 & (k + 4 - i)) { ret = (ret - ans + ha) % ha; } else { ret = (ret + ans) % ha; } } return ret; } int main() { int T; scanf("%d", &T); while(T --) { scanf("%d%lld%lld%lld", &k, &a, &n, &d); process(); printf("%lld\n", h(n)); } return 0; }
[LibreOJ 2249][NOI2014]购票
在紧张刺激的等待之后终于肝掉了这道题……
本题的DP方程长成这样(其中\(a\)指\(v\)的某个满足距离限制的祖先,\(d_v\)指\(v\)到根的路径长):
\[f_v = min(f_a + p_v(d_v - d_a) + q_v)\]
化简之后发现:
\[f_v = q_v + p_v d_v + min(f_a - p_v d_a)\]
利用\(min\)中那一块很容易发现是截距式……但是问题在于,我们的转移来源是树上的连续一段祖先,怎样维护他们的凸包?
答案很狂暴啊……用树链剖分套上向量集那题的线段树套凸包,然后完了……
(注意一点细节:本题因为数据范围过大,故可能存在两个向量叉乘爆long long,所以在求凸包时如果直接用叉积判断是否需要删点会炸掉,建议用斜率判断)
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <vector> #include <queue> #include <deque> #include <cmath> #include <set> #include <climits> using ll = long long; using T = ll; using R = long double; const R eps = 1e-8; int sign(R x) { if(fabsl(x) < eps) { return 0; } else { if(x > (R(0.00))) { return 1; } else { return -1; } } } struct Point { T x, y; Point(T qx = 0LL, T qy = 0LL) { x = qx; y = qy; } }; using Vector = Point; Vector operator +(const Vector &a, const Vector &b) { return Vector(a.x + b.x, a.y + b.y); } Vector operator -(const Point &a, const Point &b) { return Vector(a.x - b.x, a.y - b.y); } Vector operator *(const Vector &a, T lam) { return Vector(a.x * lam, a.y * lam); } Vector operator *(T lam, const Vector &a) { return Vector(a.x * lam, a.y * lam); } inline T dot(const Vector &a, const Vector &b) { return (a.x * b.x + a.y * b.y); } inline T times(const Vector &a, const Vector &b) { return (a.x * b.y - a.y * b.x); } inline bool cmp(const Point &a, const Point &b) { if(a.x == b.x) { return a.y < b.y; } else { return a.x < b.x; } } inline R slope(const Vector &a) { R dx = a.x, dy = a.y; return (dy / dx); } inline void andrew(Point *P, int L, std::vector<Point> &bot, std::vector<Point> &top) { std::sort(P + 1, P + 1 + L, cmp); for(int i = 1; i <= L; i ++) { if(i != 1 && (P[i].x == P[i - 1].x)) continue; while(bot.size() > 1 && sign(slope(P[i] - bot.back()) - slope(bot.back() - bot[bot.size() - 2])) <= 0) { bot.pop_back(); } bot.push_back(P[i]); } for(int i = L; i >= 1; i --) { if(i != L && (P[i].x == P[i + 1].x)) continue; while(top.size() > 1 && sign(slope(P[i] - top.back()) - slope(top.back() - top[top.size() - 2])) <= 0) { top.pop_back(); } top.push_back(P[i]); } std::reverse(top.begin(), top.end()); } const int maxn = 200005; const int maxno = maxn << 2; const int N = 200000; bool zen[maxno]; std::vector<Point> bot[maxno], top[maxno]; Point P[maxn]; inline void maintain(int o, int L, int R) { static Point tmp[maxn]; const int lc = o << 1, rc = o << 1 | 1; const bool used = zen[o]; zen[o] = (zen[lc] && zen[rc]); if(zen[o] != used) { std::copy(P + L, P + R + 1, tmp + 1); int len = R - L + 1; andrew(tmp, len, bot[o], top[o]); } } void modify(int o, int L, int R, const int &p, const Point &v) { if(L == R) { zen[o] = true; P[L] = v; bot[o].push_back(v); top[o].push_back(v); } else { const int M = (L + R) / 2; if(p <= M) { modify(o << 1, L, M, p, v); } else { modify(o << 1 | 1, M + 1, R, p, v); } maintain(o, L, R); } } inline T calc_ans(T k, const Point &v) { return v.y - k * v.x; } inline T search(const std::vector<Point> &vec, const T &k) { int l = 0, r = vec.size() - 1; while(r - l > 2) { int lm = (l * 2 + r) / 3, rm = (2 * r + l) / 3; if((calc_ans(k, vec[lm]) > calc_ans(k, vec[rm]))) { l = lm; } else { r = rm; } } T ans = LLONG_MAX; for(int i = l; i <= r; i ++) { ans = std::min(ans, calc_ans(k, vec[i])); } return ans; } T query(int o, int L, int R, const int &ql, const int &qr, const T &k) { if(ql <= L && R <= qr) { return search(bot[o], k); } else { int M = (L + R) / 2; T ans = LLONG_MAX; if(ql <= M) { ans = std::min(ans, query(o << 1, L, M, ql, qr, k)); } if(qr > M) { ans = std::min(ans, query(o << 1 | 1, M + 1, R, ql, qr, k)); } return ans; } } int first[maxn]; int next[maxn << 1], to[maxn << 1]; ll dist[maxn << 1]; void add_edge(int u, int v, ll d) { static int cnt = 0; cnt ++; next[cnt] = first[u]; first[u] = cnt; to[cnt] = v; dist[cnt] = d; } int fa[maxn], dep[maxn], hson[maxn]; ll d[maxn]; int siz[maxn]; int bs[maxn][18]; void dfs_1(int x, int f = -1, int depth = 1) { fa[x] = bs[x][0] = f; dep[x] = depth; siz[x] = 1; int max_siz = 0; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != f) { d[v] = d[x] + dist[i]; dfs_1(v, x, depth + 1); siz[x] += siz[v]; if(siz[v] > max_siz) { hson[x] = v; max_siz = siz[v]; } } } } int dfn[maxn], tid[maxn], up[maxn]; void dfs_2(int x, int a = 1, int f = 0) { static int cnt = 0; dfn[x] = ++ cnt; tid[cnt] = x; up[x] = a; if(hson[x]) { dfs_2(hson[x], a, x); } else { return; } for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != f && v != hson[x]) { dfs_2(v, v, x); } } } int k_anc(int x, ll k) { int yx = x; for(int j = 17; j >= 0; j --) { int a = bs[x][j]; if(a != -1 && d[yx] - d[a] <= k) { x = a; } } #ifdef LOCAL printf("%d's %lld-th anc : %d\n", yx, k, x); #endif return x; } int n; ll get_up(int x, int anc, ll k) { ll ans = LLONG_MAX; while(up[x] != up[anc]) { ans = std::min(ans, query(1, 1, n, dfn[up[x]], dfn[x], k)); x = fa[up[x]]; } return std::min(ans, query(1, 1, n, dfn[anc], dfn[x], k)); } ll p[maxn], q[maxn], l[maxn]; ll f[maxn]; void dp(int x) { #ifdef LOCAL printf("processing %d...\n", x); printf("d : %lld\n", d[x]); #endif if(x != 1) { #ifdef LOCAL printf("b : %lld\n", get_up(fa[x], k_anc(x, l[x]), p[x])); #endif f[x] = get_up(fa[x], k_anc(x, l[x]), p[x]) + d[x] * p[x] + q[x]; } else { f[x] = 0; } #ifdef LOCAL printf("ans : %lld\n", f[x]); #endif modify(1, 1, n, dfn[x], Point(d[x], f[x])); for(int i = first[x]; i; i = next[i]) { int v = to[i]; dp(v); } } int main() { int t; scanf("%d%d", &n, &t); for(int i = 2; i <= n; i ++) { int father; T s; scanf("%d%lld%lld%lld%lld", &father, &s, &p[i], &q[i], &l[i]); add_edge(father, i, s); } memset(bs, -1, sizeof(bs)); dfs_1(1); dfs_2(1); for(int j = 1; (1 << j) < n; j ++) { for(int i = 1; i <= n; i ++) { int a = bs[i][j - 1]; if(a != -1) { bs[i][j] = bs[a][j - 1]; } } } dp(1); for(int i = 2; i <= n; i ++) { printf("%lld\n", f[i]); } return 0; }
[LibreOJ 2353][NOI2007]货币兑换
emmm做了一下这道神题……(我可能是少有的用动态凸包苟的?)
首先DP方程长这样:
\[f_i = max(f_{i - 1}, f_j\cdot\frac{A_iR_j+B_i}{A_jR_j+B_j})\]
然后这个方程炒鸡复杂……首先\(f_{i - 1}\)不要管了,然后设\(a_i = \frac{f_i}{A_iR_i + B_i}\)。在xjb推了一番之后我们终于得到了截距式……
\[-a_j R_j \frac{A_i}{B_i} + \frac{f_i}{B_i} = a_j\]
但是这玩意太毒瘤了……斜率不可能单调的,这还好,在凸壳上二分/三分一下即可。但问题在于,横坐标也不单调……
这个时候就需要动态维护凸包了(其实是我不会CDQ),我直接把我向量集那题的二进制分组线段树搬了过来……(逃
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <vector> #include <cmath> #include <climits> #include <deque> #include <cassert> using R = double; const R eps = 1e-8; int sign(R x) { if(fabs(x) < eps) { return 0; } else { if(x > 0.00) { return 1; } else { return -1; } } } struct Point { R x, y; Point(R qx = 0, R qy = 0) { x = qx; y = qy; } }; using Vector = Point; Vector operator +(const Vector &a, const Vector &b) { return Vector(a.x + b.x, a.y + b.y); } Vector operator -(const Point &a, const Point &b) { return Vector(b.x - a.x, b.y - a.y); } Vector operator *(const Vector &a, R lam) { return Vector(a.x * lam, a.y * lam); } Vector operator *(R lam, const Vector &a) { return Vector(a.x * lam, a.y * lam); } inline R dot(const Vector &a, const Vector &b) { return (a.x * b.x + a.y * b.y); } inline R times(const Vector &a, const Vector &b) { return (a.x * b.y - a.y * b.x); } inline bool cmp(const Point &a, const Point &b) { if(sign(a.x - b.x) == 0) { return a.y < b.y; } else { return a.x < b.x; } } inline void andrew(Point *P, int L, std::vector<Point> &bot, std::vector<Point> &top) { std::sort(P + 1, P + 1 + L, cmp); for(int i = 1; i <= L; i ++) { if(i != 1 && sign(P[i].x - P[i - 1].x) == 0) continue; while(bot.size() > 1 && sign(times(P[i] - bot.back(), bot.back() - bot[bot.size() - 2])) >= 0) { bot.pop_back(); } bot.push_back(P[i]); } for(int i = L; i >= 1; i --) { if(i != L && sign(P[i].x - P[i + 1].x) == 0) continue; while(top.size() > 1 && sign(times(P[i] - top.back(), top.back() - top[top.size() - 2])) >= 0) { top.pop_back(); } top.push_back(P[i]); } std::reverse(top.begin(), top.end()); } const int maxn = 1000005; const int N = 1000000; const int maxno = maxn << 2; bool zen[maxno]; std::vector<Point> bot[maxno], top[maxno]; Point P[maxn]; inline void maintain(int o, int L, int R) { static Point tmp[maxn]; const int lc = o << 1, rc = o << 1 | 1; const bool used = zen[o]; zen[o] = (zen[lc] && zen[rc]); if(zen[o] != used) { std::copy(P + L, P + R + 1, tmp + 1); int len = R - L + 1; andrew(tmp, len, bot[o], top[o]); } } void modify(int o, int L, int R, const int &p, const Point &v) { if(L == R) { zen[o] = true; P[L] = v; bot[o].push_back(v); top[o].push_back(v); } else { const int M = (L + R) / 2; if(p <= M) { modify(o << 1, L, M, p, v); } else { modify(o << 1 | 1, M + 1, R, p, v); } maintain(o, L, R); } } inline R calc_ans(R k, const Point &v) { return v.y - k * v.x; } inline R search(const std::vector<Point> &vec, const R &k) { int l = 0, r = vec.size() - 1; while(r - l > 2) { int lm = (l * 2 + r) / 3, rm = (2 * r + l) / 3; if(sign(calc_ans(k, vec[lm]) - calc_ans(k, vec[rm])) == 1) { r = rm; } else { l = lm; } } R ans = INT_MIN; for(int i = l; i <= r; i ++) { ans = std::max(ans, calc_ans(k, vec[i])); } return ans; } R query(int o, int L, int R, const int &ql, const int &qr, const double &k) { if(ql <= L && R <= qr) { return search(top[o], k); } else { int M = (L + R) / 2; double ans = INT_MIN; if(ql <= M) { ans = std::max(ans, query(o << 1, L, M, ql, qr, k)); } if(qr > M) { ans = std::max(ans, query(o << 1 | 1, M + 1, R, ql, qr, k)); } return ans; } } int n, s; R A[maxn], B[maxn], Rate[maxn]; R f[maxn]; R dp() { static double a[maxn]; f[0] = s; f[1] = s; a[1] = f[1] / (A[1] * Rate[1] + B[1]); modify(1, 1, n, 1, Point(a[1] * Rate[1], a[1])); for(int i = 2; i <= n; i ++) { f[i] = query(1, 1, n, 1, i - 1, -A[i] / B[i]) * B[i]; f[i] = std::max(f[i], f[i - 1]); a[i] = f[i] / (A[i] * Rate[i] + B[i]); if(i < n) modify(1, 1, n, i, Point(a[i] * Rate[i], a[i])); } return f[n]; } int main() { scanf("%d%d", &n, &s); for(int i = 1; i <= n; i ++) { scanf("%lf%lf%lf", &A[i], &B[i], &Rate[i]); } printf("%.3lf\n", dp()); return 0; }