HAOI2015 简要题解

「HAOI2015」树上染色

题意

有一棵点数为 N 的树,树边有边权。给你一个在 0N 之内的正整数 K,你要在这棵树中选择 K 个点,将其染成黑色,并将其他的 NK 个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。

问收益最大值是多少。

N2000, 0KN

题解

树上距离,我们要么考虑差分深度,要么考虑一条边对于多少个点对进行贡献。

前者显然是不好考虑的,可以考虑后者。

fi,ji 子树内有 j 个黑点的最优答案。不难发现这个直接做一个树上背包即可,背包的权值就是这条边被多少对同色点对穿过乘上这条边的权值。

由于树上两点在 lca 贡献一次复杂度,那么复杂度是 O(n2) 的。

代码

#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 ("2124.in", "r", stdin); freopen ("2124.out", "w", stdout); #endif } const int N = 2010, M = N << 1; using ll = long long; int n, K; ll dp[N][N], tmp[N]; int Head[N], Next[M], to[M], val[M], e = 0; inline void add_edge(int u, int v, int w) { to[++ e] = v; Next[e] = Head[u]; val[e] = w; Head[u] = e; } int sz[N]; void Dfs(int u, int fa = 0) { for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) if (v != fa) { Dfs(v, u); Set(tmp, 0); For (j, 0, min(K, sz[u])) For (k, 0, min(K, sz[v])) { int Pair = k * (K - k) + (sz[v] - k) * ((n - K) - (sz[v] - k)); chkmax(tmp[j + k], dp[u][j] + dp[v][k] + 1ll * Pair * val[i]); } Cpy(dp[u], tmp); sz[u] += sz[v]; } Fordown (i, min(K, ++ sz[u]), 0) dp[u][i] = max(i ? dp[u][i - 1] : 0ll, dp[u][i]); } int main () { File(); n = read(); K = read(); For (i, 1, n - 1) { int u = read(), v = read(), w = read(); add_edge(u, v, w); add_edge(v, u, w); } Dfs(1); printf ("%lld\n", dp[1][K]); return 0; }

「HAOI2015」树上操作

题意

有一棵点数为 N 的树,以点 1 为根,且树有点权。然后有 M 个操作,分为三种:

  1. 把某个节点 x 的点权增加 a
  2. 把某个节点 x 为根的子树中所有点的点权都增加 a
  3. 询问某个节点 x 到根的路径中所有点的点权和。

N,M105

题解

树剖裸题,没什么好说的,复杂度是 O(nlog2n)

代码

#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 ll = long long; 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 ("2125.in", "r", stdin); freopen ("2125.out", "w", stdout); #endif } const int N = 2e5 + 1e3; int n; #define lowbit(x) (x & -x) template<int Maxn> struct Fenwick_Tree { ll sum1[Maxn], sum2[Maxn]; inline void Add(int pos, int uv) { for (int tmp = pos; pos <= n; pos += lowbit(pos)) sum1[pos] += uv, sum2[pos] += 1ll * tmp * uv; } ll Sum(int pos) { ll res = 0; for (int tmp = pos; pos; pos -= lowbit(pos)) res += 1ll * (tmp + 1) * sum1[pos] - sum2[pos]; return res; } inline void Update(int ul, int ur, int val) { Add(ul, val); if (ur < n) Add(ur + 1, -val); } inline ll Query(int ql, int qr) { return Sum(qr) - Sum(ql - 1); } }; Fenwick_Tree<N> T; int dep[N], fa[N]; vector<int> G[N]; int son[N], sz[N]; void Dfs_Init(int u, int from) { sz[u] = 1; dep[u] = dep[fa[u] = from] + 1; for (int v : G[u]) if (v != from) { Dfs_Init(v, u); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } int dfn[N], efn[N], top[N]; void Dfs_Part(int u) { static int clk = 0; dfn[u] = ++ clk; top[u] = son[fa[u]] == u ? top[fa[u]] : u; if (son[u]) Dfs_Part(son[u]); for (int v : G[u]) if (v != son[u] && v != fa[u]) Dfs_Part(v); efn[u] = clk; } int val[N]; int main () { File(); n = read(); int m = read(); For (i, 1, n) val[i] = read(); For (i, 1, n - 1) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } Dfs_Init(1, 0); Dfs_Part(1); For (i, 1, n) T.Update(dfn[i], dfn[i], val[i]); For (i, 1, m) { int opt = read(), x = read(); if (opt <= 2) T.Update(dfn[x], opt == 1 ? dfn[x] : efn[x], read()); if (opt == 3) { ll ans = 0; for (int u = x; u; u = fa[top[u]]) ans += T.Query(dfn[top[u]], dfn[u]); printf ("%lld\n", ans); } } return 0; }

「HAOI2015」数组游戏

题意

有一个长度为 N 的数组,甲乙两人在上面进行这样一个游戏:首先,数组上有一些格子是白的,有一些是黑的。然后两人轮流进行操作。每次操作选择一个白色的格子,假设它的下标为 x 。接着,选择一个大小在 1n/x 之间的整数 k,然后将下标为 x2x、...、kx 的格子都进行颜色翻转。不能操作的人输。

现在甲(先手)有一些询问。每次他会给你一个数组的初始状态,你要求出对于这种初始状态他是否有必胜策略。

N1000000000K,W100

题解

这是个经典的翻硬币游戏,可以参考一下 Candy? 的博客

每一个硬币可以看成独立的子游戏,所以:

局面的 SG 值为局面中每个正面朝上的棋子单一存在时的 SG 值的异或和。

SG(x) 为第 x 位为白的时候的 SG 值,那么有

SG(x)=mexi=2n/x{j=2iSG(jx)}

这个直接暴力做是 O(nlnn) 的。

SG 函数通常考虑的优化就是找规律,打打表,不难发现:

i,j 如果有 ni=nj 那么有 SG(i)=SG(j)

这个直接上整除分块就好了,然后枚举倍数的时候只需要枚举在分的块中的数,复杂度好像是 O(n3/4) 的。

总结

翻硬币问题是一种经典的 nim 问题,可以设 SG 函数。

代码

#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 ("2126.in", "r", stdin); freopen ("2126.out", "w", stdout); #endif } const int N = 1e5 + 1e3; int n, d, k, sg[N], App[N]; int id1[N], id2[N], clk = 0; #define id(x) (x <= d ? id1[x] : id2[n / (x)]) int main () { File(); n = read(); d = sqrt(n + .5); for (int i = 1; i <= n; i = n / (n / i) + 1) { int x = n / i, v = 0; id(n / i) = App[0] = ++ clk; for (int j = x << 1, nextj; j <= n; j = nextj + x) { nextj = (n / (n / j)) / x * x; App[v ^ sg[id(j)]] = clk; if (((nextj - j) / x + 1) & 1) v ^= sg[id(j)]; } while (App[sg[id(x)]] == clk) ++ sg[id(x)]; } int cases = read(); while (cases --) { int res = 0; For (i, 1, read()) { int x = read(); res ^= sg[id(x)]; } puts(res ? "Yes" : "No"); } return 0; }

「HAOI2015」按位或

题意

刚开始你有一个数字 0,每一秒钟你会随机选择一个 [0,2n1] 的数字,与你手上的数字进行或操作。选择数字 i 的概率是 p[i] 问期望多少秒后,你手上的数字变成 2n1

n20

题解

似乎有强行推式子的做法。。。但是我好像不会 QAQ

但类似于这种无限期望,每个时刻会随机出现其中一些元素,到所有元素都出现的期望计算一般都可以用 minmax 容斥做。

即原问题是求集合中最晚的元素的期望,利用最值反演就变成求集合中最早元素的期望,即

E(maxS)=TS(1)|T|+1E(minT)

然后有

E(minS)=(TSp[T])1

我们就只需要对于每个 STSP[T] 了。

这个直接取补集,考虑补集的子集和就行了,即

TSP[T]=1ET¯p[T]

利用 FMT 就可以做到 O(n×2n) 的复杂度。

总结

minmax 当做一个黑箱来用比较好。

代码

一开始蠢了,那个补集子集和没想到,利用容斥求得并不为空的贡献。。。

#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 ("2127.in", "r", stdin); freopen ("2127.out", "w", stdout); #endif } const int N = 1 << 20; const double eps = 1e-8; int n, maxsta; double p[N], prob[N], sum[N]; void FMT(double *f) { Rep (j, n) Rep (i, maxsta) if (i >> j & 1) f[i] += f[i ^ (1 << j)]; } int main () { File(); n = read(); maxsta = 1 << n; int all = maxsta - 1; int cur = 0; Rep (i, maxsta) { scanf ("%lf", &p[i]); if (p[i] > eps) cur |= i; } if (cur != maxsta - 1) return puts("INF"), 0; Rep (i, maxsta) sum[i] = p[all ^ i]; FMT(sum); Rep (i, maxsta) if (i) prob[i] = sum[all ^ i] * (__builtin_popcount(i) & 1 ? 1 : -1); FMT(prob); double ans = .0; Rep (i, maxsta) if (i) ans += (__builtin_popcount(i) & 1 ? 1 : -1) / prob[i]; printf ("%.7lf\n", ans); return 0; }

「HAOI2015」数字串拆分

题意

你有一个长度为 n 的数字串。定义 f(S) 为将 S 拆分成若干个 1m 的数的和的方案数。

你可以将这个数字串分割成若干个数字(允许前导 0 ),将他们加起来,求 f 的和。已知字符串和 m 后求答案,对 998244353 取模。

字符串长度不超过 500m5

题解

首先要解决的问题就是给定一个字符串 S 如何快速求 f(S) ,注意到 m 很小,而 S 很大,不难想到矩阵快速幂。

转移是很显然的 f(S)=i=1mf(Si) ,搞个矩阵作为转移系数。

然后令 dpi 为以 i 结尾的答案矩阵,转移显然 dpi=j<idpj×transji

我们唯一要处理的就是 transji ,可以利用二进制下矩阵快速幂解决,但显然高精度十进制转二进制很不方便。可以考虑直接预处理关于十进制的矩阵幂就行了。

复杂度是 O(|S|2m3) 的。

总结

有时候不一定要纠结着二进制,可能没有那么好解决,十进制是一种不错的方案。

代码

#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 ("2128.in", "r", stdin); freopen ("2128.out", "w", stdout); #endif } const int N = 510, M = 7, Mod = 998244353; char str[N]; int m; struct Matrix { int a[M][M]; Matrix() { Set(a, 0); } void Unit() { For (i, 1, m) a[i][i] = 1; } inline Matrix friend operator * (const Matrix &lhs, const Matrix &rhs) { Matrix res; For (i, 1, m) For (k, 1, m) For (j, 1, m) res.a[i][k] = (res.a[i][k] + 1ll * lhs.a[i][j] * rhs.a[j][k]) % Mod; return res; } inline Matrix friend operator + (const Matrix &lhs, const Matrix &rhs) { Matrix res; For (i, 1, m) For (j, 1, m) res.a[i][j] = (lhs.a[i][j] + rhs.a[i][j]) % Mod; return res; } }; Matrix trans[N][10], Base, f[N]; int dp[N]; int main () { File(); scanf ("%s", str + 1); m = read(); int n = strlen(str + 1); For (i, 1, m - 1) Base.a[i][i + 1] = 1; For (i, 1, m) Base.a[m][i] = 1; trans[0][0].Unit(); trans[0][1] = Base; For (i, 2, 9) trans[0][i] = trans[0][i - 1] * Base; For (i, 1, n) { trans[i][0].Unit(); trans[i][1] = trans[i - 1][1] * trans[i - 1][9]; For (j, 2, 9) trans[i][j] = trans[i][j - 1] * trans[i][1]; } dp[0] = 1; For (i, 1, 10) For (j, 1, min(i, m)) dp[i] += dp[i - j]; For (i, 1, m) f[0].a[i][1] = dp[i - 1]; For (i, 1, n) { Matrix now; now.Unit(); Fordown (j, i, 1) { now = now * trans[i - j][str[j] ^ 48]; f[i] = f[i] + now * f[j - 1]; } } printf ("%d\n", f[n].a[1][1]); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10366751.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(408)  评论(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】
点击右上角即可分享
微信分享提示