SDOI2017 Round2 详细题解

这套题实在是太神仙了。。做了我好久。。。好多题都是去搜题解才会的 TAT。

剩的那道题先咕着,如果省选没有退役就来填吧。

「SDOI2017」龙与地下城

题意

Y 次骰子,骰子有 X 面,每一面的概率均等,取值为 [0,X) ,问最后取值在 [a,b] 之间的概率。

一个浮点数,绝对误差不超过 0.013579 为正确。

数据范围

每组数据有 10 次询问。

100% 的数据,T102X201Y2000000AB(X1)Y ,保证满足 Y>800 的数据不超过 2 组。

题解

一开始不难想到一个暴力做法,设 P(x) 为丢一次骰子的概率生成函数,也就是 P(x)=i=0X11Xxi ,我们其实就是求 PY(x)xaxb 项的系数之和。

我们考虑一开始用 FFT 求出 2k(X1)×Y 个单位根的点值。

那么多项式乘法就可以变为点值的乘法,也就是说每个点值对应变成它的 Y 次方,就可以在 O(XYlogXY) 的时间内处理出这个多项式的 Y 次方的答案啦。

这样在考场应该只有 6070pts 但是由于 LOJ 机子神速,可以直接过。。。代码在这里


至于正解,与这个前面提到那个暴力解法是完全没有关系的。。

如果你足够聪明,看懂了出题人的提示,那就会做啦。

需要知道一个 中心极限定理

以下内容来自维基百科

中心极限定理说明,在适当的条件下,大量相互独立随机变量的均值经适当标准化后依分布收敛于 正态分布

设随机变量 X1,X2,,Xn 独立同分布,并且具有有限的 数学期望方差E(Xi)=μ,D(Xi)=σ20(i=1,2,,n)

X¯=1ni=1nXi,ζn=x¯μσ/n ,则 limnP(ζnz)=Φ(z)

其中 Φ(z) 是标准正态分布函数。

如果你回去上过文化课,学过统计中的正态分布应该就会啦。

说人话??好吧。。

n 个独立同分布,且具有有限的数学期望 μ 和方差 σ2 的随机变量,他们的平均值的分布函数在 n 时近似为位置参数为 μ 尺度参数为 σ 的正态分布。

至于标准正态分布函数其实就是:

Φ(x)=1σ2πexp((xμ)22σ2)

也就是最后答案在 [a,b] 的概率为 P(aXb)=abΦ(x)dx

至于这个积分是个初等函数,并且极其光滑,那么我们利用辛普森就可以积出来了。

但是有很多点值都趋近于 0 ,会被卡。

此时利用高中学的 3σ 原则 (2017全国一卷理科数学) ,可知, P(μ3σ<xμ+3σ)0.9974

落在剩下区间的概率不足 0.3% 根据题目要求的精度基本可以忽略。

那么得到了最后的解法,在 Y 较小用 FFT 或者暴力卷积实现,在 Y 比较大的时候用辛普森求积分就好啦。

总结

独立同分布变量可以利用正态分布函数求积分快速计算估计值。

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; using vd = vector<double>; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2267.in", "r", stdin); freopen ("2267.out", "w", stdout); #endif } const int N = 1610; struct Complex { double re, im; inline Complex friend operator + (const Complex &lhs, const Complex &rhs) { return (Complex) {lhs.re + rhs.re, lhs.im + rhs.im}; } inline Complex friend operator - (const Complex &lhs, const Complex &rhs) { return (Complex) {lhs.re - rhs.re, lhs.im - rhs.im}; } inline Complex friend operator * (const Complex &lhs, const Complex &rhs) { return (Complex) {lhs.re * rhs.re - lhs.im * rhs.im, lhs.re * rhs.im + lhs.im * rhs.re}; } }; const double Pi = acos(-1.0), eps = 1e-15; namespace poly { const int Maxn = 1 << 24; int len, rev[Maxn]; void FFT(Complex *P, int opt) { Rep (i, len) if (i < rev[i]) swap(P[i], P[rev[i]]); for (int i = 2, p = 1; i <= len; p = i, i <<= 1) { Complex Wi = (Complex) {cos(2 * Pi / i), opt * sin(2 * Pi / i)}; for (int j = 0; j < len; j += i) { Complex x = (Complex) {1, 0}; for (int k = 0; k < p; ++ k, x = x * Wi) { Complex u = P[j + k], v = x * P[j + k + p]; P[j + k] = u + v; P[j + k + p] = u - v; } } } if (!~opt) Rep (i, len) P[i].re /= len; } Complex A[Maxn], B[Maxn], C[Maxn]; inline vd operator * (const vd &a, const vd &b) { int na = a.size() - 1, nb = b.size() - 1, nc = na + nb, cnt = 0; for (len = 1; len <= nc; len <<= 1) ++ cnt; Rep (i, len) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1)); Rep (i, len) A[i] = (Complex) {i <= na ? a[i] : 0, 0}; Rep (i, len) B[i] = (Complex) {i <= nb ? b[i] : 0, 0}; FFT(A, 1); FFT(B, 1); Rep (i, len) C[i] = A[i] * B[i]; FFT(C, -1); vd res(nc + 1); For (i, 0, nc) res[i] = C[i].re; return res; } } template<typename T> inline T fpm(T x, int power) { T res = x; -- power; for (; power; power >>= 1, x = x * x) if (power & 1) res = res * x; return res; } double mu, sigma; #define sqr(x) ((x) * (x)) double f(double x) { return exp(- sqr(x - mu) / (2 * sqr(sigma))) / (sqrt(2 * Pi) * sigma); } double Asr(double l, double r) { return (r - l) * (f(l) + 4 * f((l + r) / 2) + f(r)) / 6; } double Simpson(double l, double r, double area) { double mid = (l + r) / 2, la = Asr(l, mid), ra = Asr(mid, r); if (fabs(area - la - ra) < eps) return la + ra; return Simpson(l, mid, la) + Simpson(mid, r, ra); } int main () { File(); int cases = read(); while (cases --) { int x = read(), y = read(); using namespace poly; if (y <= 2000) { int nc = (x - 1) * y, cnt = 0; for (len = 1; len <= nc; len <<= 1) ++ cnt; Rep (i, len) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1)); Rep (i, len) A[i] = (Complex) {i < x ? 1.0 / x : 0, 0}; FFT(A, 1); Rep (i, len) A[i] = fpm(A[i], y); FFT(A, -1); vd ans(nc + 1); Rep (i, ans.size()) { ans[i] = A[i].re; if (i) ans[i] += ans[i - 1]; } For (i, 1, 10) { int l = read(), r = read(); printf ("%.6lf\n", ans[r] - (l ? ans[l - 1] : 0)); } } else { mu = (x - 1) / 2.0 * y; sigma = sqrt(y * (1.0 * x * x - 1) / 12.0); For (i, 1, 10) { double l = max((double)read(), mu - 3 * sigma); double r = min((double)read(), mu + 3 * sigma); double ans = l <= r ? Simpson(l, r, Asr(l, r)) : 0; printf ("%.6lf\n", ans); } } } return 0; }

「SDOI2017」苹果树

题意

n 个点的一颗树,每个点有 ai 个物品,权值为 vi ,可以取走 t 个物品,取一个物品的时候它的父亲至少也要取一个物品。

假设取了物品的节点最大深度为 h 要求 thk

最大化最后取物品的权值和。

数据范围

Q 为数据组数。

1Q51n200001k5000001nk250000001ai1081vi100

题解

首先忽略 t 的要求,就是 树上依赖多重背包

这个怎么做呢?树上依赖背包其实就是你考虑按树的后序遍历(先儿子后根)标号。

fi,j 为后序遍历前 i 个节点,背包大小为 j 的时候的最大权值。

然后依次转移每个点,如果这个点要选的话,那么它可以直接从 fi1,jv+w 转移过来,否则它不选就只能从 fisz,j 转移过来(也就是子树内一个都不能选)。

然后由于是多重背包,二进制分组被卡了,只能单调队列优化,物品大小为 1 的话就不需要按模数分类了。具体来说,对于 fi1,jkv+kw(v=1) 可以拆成 (fi1,jk(jk)w)+jw 的形式。前者只与 jk 有关,然后是要求 [jc,j] 这个的最大值,这很显然是可以用单调队列的。

最后考虑 thk 的限制是什么意思,其实就是根到一个节点路径上每个点都可以免费拿一个,剩下的拿 k 个。显然我们是选一个叶子节点是最优的,但注意不是选最深的那个叶子!

然后我们只需要考虑,这条路上都少一个容量的答案如何快速算,考虑拆点,把 i 号点拆个容量为 ai1 价值为 vi 的节点 i 出来,把 ii 连一条边。

此时原来树上每个点的容量就恰好是 1 了,新加的我们可以在外面算。

然后不难发现除了这条链的子树就是对应着后序遍历上的两段区间,我们正反各做一遍 dp 就能求出来了。

最后复杂度是 O(nk) 的。

总结

经典背包还是要多记一下。图论还是要多想下拆点,有可能就好做许多。

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2268.in", "r", stdin); freopen ("2268.out", "w", stdout); #endif } const int N = 40100, K = 500100, M = 5.5e7 + 10; int n, m; int dfn[2][N], lis[2][N], sz[N], clk; vector<int> G[N]; int w[N], c[N], sum[N]; void Dfs_Init(int u, int id) { sz[u] = 1; sum[u] += w[u]; for (int v : G[u]) sum[v] = sum[u], Dfs_Init(v, id), sz[u] += sz[v]; lis[id][dfn[id][u] = ++ clk] = u; } #define dp(id, x, y) dp[id][(x) * (m + 1) + (y)] int dp[2][M], val[K], que[K], fr, tl; void Dp(int id) { For (i, 1, clk) { int u = lis[id][i]; For (j, 0, m) dp(id, i, j) = dp(id, i - sz[u], j); fr = 1; tl = 0; For (j, 0, m) { while (fr <= tl && j - que[fr] > c[u]) ++ fr; if (fr <= tl) chkmax(dp(id, i, j), val[que[fr]] + j * w[u]); val[j] = dp(id, i - 1, j) - j * w[u]; while (fr <= tl && val[que[tl]] <= val[j]) -- tl; que[++ tl] = j; } } } bool leaf[N]; int tot; void Init() { Set(leaf, true); For (i, 1, n) G[i].clear(); } int main () { File(); int cases = read(); while (cases --) { Init(); tot = n = read(), m = read(); int rt = 0; For (i, 1, n) { int fa = read(); if (fa) { leaf[fa] = false; G[fa].push_back(i); } else rt = i; int ai = read(), vi = read(); w[i] = vi; c[i] = 1; if (ai > 1) { int id = ++ tot; w[id] = vi; c[id] = ai - 1; G[i].push_back(id); } } Rep (id, 2) { sum[rt] = clk = 0, Dfs_Init(rt, id), Dp(id); For (i, 1, n) reverse(G[i].begin(), G[i].end()); For (i, 1, n) For (j, 1, m) chkmax(dp(id, i, j), dp(id, i, j - 1)); } int ans = 0; For (i, 1, n) if (leaf[i]) { For (j, 0, m) chkmax(ans, dp(0, dfn[0][i] - 1, j) + dp(1, dfn[1][i] - sz[i], m - j) + sum[i]); } For (i, 1, tot) For (j, 0, m) dp(0, i, j) = dp(1, i, j) = 0; printf ("%d\n", ans); } return 0; }

「SDOI2017」切树游戏

题意

一棵有 n 个点的树 T ,节点带权,两种操作:

Change x y : 将编号为 x 的结点的权值修改为 y

Query k : 询问有多少棵 T 的非空连通子树,满足其中所有点权值的异或和恰好为 k

数据范围

n,q3×104,m128 修改操作不超过 104

题解

动态 dp 经典题目,参考了 dy0607 的博客 orz dy。

先考虑暴力怎么做,设 f[i][j] 表示包含 i 的连通块( i 为连通块中深度最小的节点),异或和为 j 的方案数,从下往上DP即可。

注意到在合并子树信息时实际上是在做异或卷积,可以用 FWT 优化,注意 FWT 不存在循环卷积的性质十分优秀,加减乘除全都可以在点值上做。那么可以全程用 FWT 之后的点值计算,算答案时再 IFWT 回去,此时的复杂度为 O(nqm+qmlogm)

为了动态修改,我们利用重链剖分变成序列问题,设 i 重儿子为 j ,轻儿子集合为 child(i)

利用集合幂级数的形式来定义 dp 转移:

Fi(z)=j=0m1f[i][j]zj=Fj(z)×zvi×(cchild(i)Fc(z))+z0

注意 z0 对应着转移时候的空树,统计答案的时候应该去掉。我们用另外一个幂级数 G 统计答案:

Gi(z)=Gj(z)+(cchild(i)Gc(z))+(Fi(z)z0)

我们假设把轻儿子的幂级数设为常数,即:

Gli(z)=cchild(i)Gc(z)Fli(z)=zvi×cchild(i)Fc(z)

那么对于 (Fj,Gj,1)(Fi,Gi,1) 是一个线性变换可以写成:

(FjGj1)×(FliFli00101Gli1)=(FiGi1)

注意此处的 1 对应的就是集合幂级数的单位元 z0

我们考虑用一个线段树维护矩阵的乘积,那么我们就可以动态算出重链顶端的 F,G 了。

那么每次修改的时候我们只需要考虑那些常数要怎么改了。

对于 Gli(z) 比较好改,假设当前修改的是 cchild(i) 。那么只需要减掉之前的 G(c) 加上后来的 G(c)

但是对于 Fli(z) 就不好改了,需要变换的其实就是 Fc(z) 这个先要除掉,除的时候可能会除 0 ,看到网上有神仙做法是维护 0 的个数,我不太会。。

其实可以对于每个点在线段树上维护它轻儿子的 Fc(z) 的值,那么我们可以直接在线段树上修改然后求出 Fli(z) 的值。

其中有一个常数优化是,矩阵上 0 的跳过去,这个优化十分显著。

其实只需要维护 4 个值 a,b,c,d ,如下图。

(a1_b1_0010c1_d1_1)×(a2_b2_0010c2_d2_1)=(a1a2_b1+a1b2_0010a2c1+c2_b2c1+d1+d2_1)

复杂度是 O(qm(log2n+logm))

总结

ddp 主要就是考虑轻儿子如何贡献的,把这个当做常数递推的系数。每次修改的时候只需要修改轻儿子的贡献上来的矩阵就行了。

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define plus Plus using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2269.in", "r", stdin); freopen ("2269.out", "w", stdout); #endif } const int N = 30100, M = 128, Mod = 10007, inv2 = (Mod + 1) / 2; namespace Computation { inline void add(int &a, int b) { if ((a += b) >= Mod) a -= Mod; } inline int plus(int a, int b) { return (a += b) >= Mod ? a - Mod : a; } inline int dec(int a, int b) { return (a -= b) < 0 ? a + Mod : a; } inline int mul(int a, int b) { return 1ll * a * b % Mod; } } using namespace Computation; int n, m, q, v[N]; struct Poly { int x[M]; inline void clear() { Set(x, 0); } Poly(int id = 0) { clear(); if (id == 1) Rep (i, m) x[i] = 1; } inline Poly operator = (const Poly &rhs) { Rep (i, m) x[i] = rhs.x[i]; return *this; } inline Poly friend operator + (const Poly &lhs, const Poly &rhs) { Poly res; Rep (i, m) res.x[i] = plus(lhs.x[i], rhs.x[i]); return res; } inline Poly friend operator - (const Poly &lhs, const Poly &rhs) { Poly res; Rep (i, m) res.x[i] = dec(lhs.x[i], rhs.x[i]); return res; } inline Poly friend operator * (const Poly &lhs, const Poly &rhs) { Poly res; Rep (i, m) res.x[i] = mul(lhs.x[i], rhs.x[i]); return res; } void FWT(int opt) { for (int i = 2, p = 1; i <= m; p = i, i <<= 1) for (int j = 0; j < m; j += i) Rep (k, p) { int u = x[j + k], v = x[j + k + p]; x[j + k] = mul(plus(u, v), opt == 1 ? 1 : inv2); x[j + k + p] = mul(dec(u, v), opt == 1 ? 1 : inv2); } } inline void Out() { this -> FWT(-1); Rep (i, m) printf ("%d%c", x[i], i == m - 1 ? '\n' : ' '); this -> FWT(1); } }; struct Info { Poly a, b, c, d; inline Info friend operator * (const Info &lhs, const Info &rhs) { return (Info) { lhs.a * rhs.a, lhs.a * rhs.b + lhs.b, rhs.a * lhs.c + rhs.c, rhs.b * lhs.c + lhs.d + rhs.d}; } inline void Out() { puts("-----------"); a.Out(); b.Out(); c.Out(); d.Out(); puts("-----------"); } }; #define mid ((l + r) >> 1) #define lson o << 1, l, mid #define rson o << 1 | 1, mid + 1, r template<typename T, int maxn> struct Segment_Tree { T mulv[maxn]; void build(int o, int l, int r, T *tmp) { if (l == r) { mulv[o] = tmp[mid]; return; } build(lson, tmp); build(rson, tmp); mulv[o] = mulv[o << 1 | 1] * mulv[o << 1]; } void update(int o, int l, int r, int up, T uv) { if (l == r) { mulv[o] = uv; return; } up <= mid ? update(lson, up, uv) : update(rson, up, uv); mulv[o] = mulv[o << 1 | 1] * mulv[o << 1]; } T query(int o, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return mulv[o]; if (qr <= mid) return query(lson, ql, qr); if (ql > mid) return query(rson, ql, qr); return query(rson, ql, qr) * query(lson, ql, qr); } }; Segment_Tree<Info, N << 2> mat; Segment_Tree<Poly, N << 2> FL; #define root 1, 1, n vector<int> E[N]; int son[N], sz[N], fa[N]; void Dfs_Init(int u) { sz[u] = 1; for (int v : E[u]) if (v != fa[u]) { fa[v] = u, Dfs_Init(v); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } int top[N], dfn[N], id[N], enl[N]; void Dfs_Part(int u) { static int clk = 0; id[dfn[u] = ++ clk] = enl[u] = u; top[u] = son[fa[u]] == u ? top[fa[u]] : u; if (son[u]) Dfs_Part(son[u]), enl[u] = enl[son[u]]; for (int v : E[u]) if (v != son[u] && v != fa[u]) Dfs_Part(v); } Poly F[N], G[N], Gl[N], Fl[N], base[N]; int Beg[N], End[N], fid[N], tot = 0; void Dp(int u) { F[u] = base[v[u]]; G[u].clear(); for (int v : E[u]) if (v != fa[u] && v != son[u]) { Dp(v); F[u] = F[u] * F[v]; G[u] = G[u] + G[v]; } Gl[u] = G[u]; Fl[u] = F[u]; if (son[u]) { Dp(son[u]); F[u] = F[u] * F[son[u]]; G[u] = G[u] + G[son[u]]; } G[u] = G[u] + F[u]; F[u] = F[u] + 1; for (int v : E[u]) if (v != son[u] && v != fa[u]) if (!fid[v]) { End[u] = fid[v] = ++ tot; if (!Beg[u]) Beg[u] = fid[v]; } } void ask(int o) { Info cur = mat.query(root, dfn[o], dfn[enl[o]]); F[o] = (Fl[enl[o]] + 1) * cur.a + cur.c; G[o] = (Fl[enl[o]] + 1) * cur.b + base[v[enl[o]]] + cur.d; } void get_mat(int o) { Fl[o] = Beg[o] ? FL.query(root, Beg[o], End[o]) * base[v[o]] : base[v[o]]; mat.update(root, dfn[o], son[o] ? (Info) {Fl[o], Fl[o], 1, Gl[o]} : (Info) {1, 0, 0, 0}); } Info I[N]; Poly P[N]; int main () { File(); n = read(); m = read(); For (i, 1, n) v[i] = read(); For (i, 1, n - 1) { int u = read(), v = read(); E[u].push_back(v); E[v].push_back(u); } Rep (i, m) base[i].x[i] = 1, base[i].FWT(1); Dfs_Init(1); Dfs_Part(1); Dp(1); For (u, 1, n) { I[dfn[u]] = son[u] ? (Info) {Fl[u], Fl[u], 1, Gl[u]} : (Info) {1, 0, 0, 0}; if (fid[u]) P[fid[u]] = F[u]; } mat.build(root, I); FL.build(root, P); q = read(); while (q --) { static char str[10]; scanf ("%s", str + 1); if (str[1] == 'C') { int x = read(), y = read(); v[x] = y; for (; x; x = fa[x]) { if (fa[top[x]]) Gl[fa[top[x]]] = Gl[fa[top[x]]] - G[top[x]]; get_mat(x); if (x == 1) break; x = top[x]; ask(x = top[x]); if (fid[x]) FL.update(root, fid[x], F[x]); Gl[fa[x]] = Gl[fa[x]] + G[x]; } } else { ask(1); Poly ans = G[1]; ans.FWT(-1); printf ("%d\n", ans.x[read()]); } } return 0; }

「SDOI2017」天才黑客

题意

这道题题意看了我好久。。。

给你一个 n 个点 m 条边的有向图,同时给你一个 k 个节点的字典树。

对于每条边有两个属性,一个是它的边权 ci 另外一个是这条边会对应到字典树上一个节点 di

然后你从 1 开始走,一开始手上有个字符串 S 为空串,每次走一条边 i 需要花费的代价是 ci 加上 di 对应的字符串与 SLCP 长度,然后 S 变成 di 对应的字符串。

问从 1 号店开始到 2n 所有点的最短路的长度。

数据范围

对于 100% 的数据,T102n500001m500001k20000,保证满足 n>5000m>5000 的数据不超过 2 组。

题解

显然暴力考虑的话,我们肯定要记过来的前一条边是什么,那么就是 O(m2) 的,不够优秀。

发现从点到点时的代价是不确定的,而从边到边的代价是一定的,所以将边转化为点,点权 vi 为原图中的边权 ci ,然后以 1 为起点的边距离 disi=vi 然后跑完最短路后的每个点 x 的距离,其实就是所有指向 x 的边的 minbi=xdisi

接下来我们只需要考虑考虑如何建边,新图中点 x 到点 y 的举例其实就是 dxdy 在字典树上 lca 的深度。

那对原图中每个点考虑它相邻的所有边可以一起考虑建边,把这些边按 di 在字典树上的 dfs 序从小到大排序。

lenx=lcp(dx,dx+1) 那么有 lcp(dx,dy)=mini=xy1leni 。这个可以考虑这些节点在字典树上的虚树,两个节点对应的 lca 就是被夹在其中的相邻点对最浅的那个 lca

我们就可以考虑枚举分界点 x ,对于所有标号 x 的点与所有标号 x 的点的距离不会超过 len ,那么我们建前缀和后缀辅助点就行了。

但是注意入边和出边不能同时连一个前缀或者后缀,不然会直接“短路”。我们可以考虑搞两个前缀和后缀,一个入边连前缀出边连后缀,另外一个出边连前缀入边连后缀。

为什么要这样呢?因为入边和出边的 dfs 序的大小关系有两种,要分开讨论。

说的好像有点玄乎,挂张图。如下是样例关于点 2 建出来的图:

点数和边数都是 O(m) 的,最后跑一次 DijkstraO(mlogm) 的。

总结

最短路的题一般都是考虑重构图,降低边数/点数。

如果是取 mini=lrai 用双层图跑最短路,如果是 i=lrai 就差分跑最短路。

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define pb push_back using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2270.in", "r", stdin); freopen ("2270.out", "w", stdout); #endif } const int N = 1e6 + 1e3, M = N << 2; int n, m, k, val[N], id[N], tot; vector<int> In[N], Out[N], ch[N]; int Head[N], Next[M], to[M], weight[M], e; inline void add_edge(int u, int v, int w, bool rev = false) { if (rev) swap(u, v); to[++ e] = v; Next[e] = Head[u]; Head[u] = e; weight[e] = w; } int clk, dfn[N], anc[N][20], Log2[N], dep[N]; void Dfs_Init(int u) { dfn[u] = ++ clk; for (int v : ch[u]) dep[v] = dep[u] + 1, Dfs_Init(v), anc[v][0] = u; } inline int Lca(int x, int y) { if (dep[x] < dep[y]) swap(x, y); int gap = dep[x] - dep[y]; Fordown (i, Log2[gap], 0) if (gap >> i & 1) x = anc[x][i]; if (x == y) return x; Fordown (i, Log2[dep[x]], 0) if (anc[x][i] != anc[y][i]) x = anc[x][i], y = anc[y][i]; return anc[x][0]; } void Rebuild() { static int pre[N][2], suf[N][2]; For (i, 1, n) { struct Node { int type, num, pos; }; vector<Node> V; for (int x : In[i]) V.push_back({0, x, id[x]}); for (int x : Out[i]) V.push_back({1, x, id[x]}); sort(V.begin(), V.end(), [&](Node a, Node b) { return dfn[a.pos] < dfn[b.pos]; }); Rep (i, V.size()) Rep (id, 2) { pre[i][id] = ++ tot; if (V[i].type == id) add_edge(V[i].num, pre[i][id], 0, V[i].type); if (i) add_edge(pre[i - 1][id], pre[i][id], 0, id); } Fordown (i, V.size() - 1, 0) Rep (id, 2) { suf[i][id] = ++ tot; if (V[i].type == (id ^ 1)) add_edge(V[i].num, suf[i][id], 0, V[i].type); if (i < int(V.size()) - 1) add_edge(suf[i][id], suf[i + 1][id], 0, id); } Rep (i, V.size()) Rep (id, 2) add_edge(pre[i][id], suf[i][id], dep[V[i].pos], id); Rep (i, V.size() - 1) { int dis = dep[Lca(V[i].pos, V[i + 1].pos)]; Rep (id, 2) add_edge(pre[i][id], suf[i + 1][id], dis, id); } } } int dis[N]; bool vis[N]; void Dijkstra() { priority_queue<pair<int, int>> P; Set(dis, 0x7f); Set(vis, false); for (int v : Out[1]) P.emplace(- (dis[v] = val[v]), v); while (!P.empty()) { int u = P.top().second; P.pop(); if (vis[u]) continue; vis[u] = true; for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) if (chkmin(dis[v], dis[u] + weight[i] + val[v])) P.emplace(- dis[v], v); } } int main () { File(); int cases = read(); while (cases --) { n = read(); tot = m = read(); k = read(); Set(val, 0); Set(Head, 0); e = clk = 0; For (i, 1, n) In[i].clear(), Out[i].clear(); For (i, 1, k) ch[i].clear(); For (i, 1, m) { Out[read()].pb(i); In[read()].pb(i); val[i] = read(), id[i] = read(); } For (i, 1, k - 1) { int u = read(), v = read(); read(); ch[u].pb(v); } Dfs_Init(1); For (i, 2, k) Log2[i] = Log2[i >> 1] + 1; For (j, 1, Log2[k]) For (i, 1, k) anc[i][j] = anc[anc[i][j - 1]][j - 1]; Rebuild(); Dijkstra(); For (i, 2, n) { int ans = 0x7f7f7f7f; for (int v : In[i]) chkmin(ans, dis[v]); printf ("%d\n", ans); } } return 0; }

「SDOI2017」遗忘的集合

题意

给你一个长度为 n 的数组 f(i) ,你需要构造一个集合,满足对于所有 i 能被集合中元素凑出来的方案(只考虑出现次数,不考虑顺序)对于 p 取模为 f(i)p 为质数)。

然后解要字典序尽量小。

数据范围

对于 100% 的数据,有 1n<218106p<230i,0f(i)<p

题解

对于计数类背包我们通常考虑生成函数,令 ai{0,1} 表示 i 是否出现在集合中,那么 f 对应的生成函数就是:

F(x)=i=1n(11xi)ai

现在就变成了构造一组 ai 满足 F(x)

两边取对数那么有:

lnF(x)=i=1nailn(1xi)

如果做过各种背包套路题的就知道:

ln(1xi)=j=1xijj

这个证明可以看 zsy 大佬的博客

考虑代入前面的式子,就有

lnF(x)=i=1naij=1xijj

T=ij 交换和式,那么就有

lnF(x)=T=1xTd|Tad×dT

那么我们就只要求出 lnF(x) ,然后就可以得到 d|Tad×dT

也就是 iai=k|ikck ,其中 ck 为给定的系数。

可以莫比乌斯反演,但是没有必要。我们从前往后枚举每个 d 我们把每个 d 的倍数 T 减掉当前的值就行了。

多项式求 ln 复杂度在于多项式除法,用主定理证的 O(nlogn) 实际上常数大到飞起(还有个 MTT 。。)

总结

牢记普通生成函数的形式。

11xi=1+xi+x2i+

以及一个经典多项式对数的形式。

ln(1A(x))=i1Ai(x)i

代码

傻吊出题人出个求 ln 的题还要任意模数 FFT 。。

注意用 MTT 的话要预处理单位根来卡精度。

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2271.in", "r", stdin); freopen ("2271.out", "w", stdout); #endif } const double Pi = acos(-1.0); struct Complex { double re, im; }; inline Complex operator + (const Complex &lhs, const Complex &rhs) { return (Complex) {lhs.re + rhs.re, lhs.im + rhs.im}; } inline Complex operator - (const Complex &lhs, const Complex &rhs) { return (Complex) {lhs.re - rhs.re, lhs.im - rhs.im}; } inline Complex operator * (const Complex &lhs, const Complex &rhs) { return (Complex){lhs.re * rhs.re - lhs.im * rhs.im, lhs.re * rhs.im + lhs.im * rhs.re}; } const int Maxn = (1 << 22) + 5; int len, rev[Maxn]; Complex W[Maxn]; void FFT(Complex *P, int opt) { For (i, 0, len - 1) if (i < rev[i]) swap(P[i], P[rev[i]]); for (int i = 2, p = 1; i <= len; p = i, i <<= 1) { Rep (k, p) W[k] = (Complex){cos(2 * Pi * k / i), opt * sin(2 * Pi * k / i)}; for (int j = 0; j < len; j += i) { For (k, 0, p - 1) { Complex u = P[j + k], v = P[j + k + p] * W[k]; P[j + k] = u + v; P[j + k + p] = u - v; } } } if (!~opt) For (i, 0, len - 1) P[i].re /= len; } void FFT_Init(int n) { int cnt = 0; for (len = 1; len <= n; len <<= 1) ++ cnt; For (i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1)); } void Trans(int *a, Complex *P) { Rep (i, len) P[i] = (Complex){(double)a[i], 0}; FFT(P, 1); } const int Pow = (1 << 15) - 1; int F[Maxn], G[Maxn], CoefF[Maxn], CoefG[Maxn], res[Maxn], Mod; Complex A[Maxn], B[Maxn], C[Maxn], D[Maxn], sum[Maxn]; inline int Get_Mod(double x) { return (int)((x - floor(x / Mod) * Mod) + .5); } void Mult(Complex *a, Complex *b, int base, int opt = 1) { Rep (i, len) sum[i] = sum[i] + a[i] * b[i]; if (opt) { FFT(sum, -1); Rep (i, len) { res[i] = (res[i] + 1ll * base * Get_Mod(sum[i].re)) % Mod; sum[i] = (Complex){0, 0}; } } } void Mult(int *f, int *g, int *ans, int len) { FFT_Init(len); Rep (i, len << 1) res[i] = 0; Rep (i, len) CoefF[i] = f[i] & Pow, F[i] = f[i] >> 15; Rep (i, len) CoefG[i] = g[i] & Pow, G[i] = g[i] >> 15; Trans(CoefF, A); Trans(F, B); Trans(CoefG, C); Trans(G, D); Mult(A, C, 1); Mult(B, D, 1 << 30); Mult(A, D, 1 << 15, 0); Mult(B, C, 1 << 15); Rep (i, len << 1) ans[i] = res[i]; } inline int fpm(int x, int power) { int res = 1; for (; power; power >>= 1, x = 1ll * x * x % Mod) if (power & 1) res = 1ll * res * x % Mod; return res; } const int N = 1e6 + 1e3; int tmp[N]; void Get_Inv(int *a, int *b, int len) { if (len == 1) { b[0] = fpm(a[0], Mod - 2); return; } Get_Inv(a, b, len >> 1); Mult(a, b, tmp, len); Rep (i, len) tmp[i] = Mod - tmp[i]; tmp[0] += 2; Mult(tmp, b, b, len); } int der[N], inv[N]; void Get_Ln(int *a, int *b, int len) { For (i, 1, len - 1) der[i - 1] = 1ll * i * a[i] % Mod; Get_Inv(a, inv, len); Mult(der, inv, b, len); Fordown (i, len - 1, 0) b[i] = 1ll * b[i - 1] * fpm(i, Mod - 2) % Mod; } int x[N], f[N], ans[N]; int main () { File(); int n = read(); Mod = read(); f[0] = 1; For (i, 1, n) f[i] = read(); int len = 1; while (len <= n) len <<= 1; Get_Ln(f, f, len); For (i, 1, n) f[i] = 1ll * f[i] * i % Mod; For (i, 1, n) for (int j = i << 1; j <= n; j += i) (f[j] += Mod - f[i]) %= Mod; vector<int> ans; For (i, 1, n) if (f[i]) ans.push_back(i); printf ("%d\n", int(ans.size())); Rep (i, ans.size()) printf ("%d%c", ans[i], i == iend - 1 ? '\n' : ' '); return 0; }

「SDOI2017」文本校正

题意

给你字符串 ST ,问你是否能把 T 分成三段重组后变成 S ,如果可行则给出方案。

数据范围

T30,3n10000001Si.Tim1000000

题解

可以考虑把 T 划分成 3 段为 ABC 考虑重组后的形式,共有 3!=6 种方式。

  • ABC: 直接暴力判断即可。
  • CBA: 不会做。
  • ACB: 枚举 AC 分界点,然后???
  • BAC: 和上面是一样的。
  • CAB: 不知道
  • BCA: 和上面一种一样的。

省选没退役就来填坑。


__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10432145.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(808)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示