SCOI 2015 Day2 简要题解

「SCOI2015」小凸玩密室

题意

小凸和小方相约玩密室逃脱,这个密室是一棵有 n 个节点的完全二叉树,每个节点有一个灯泡。点亮所有灯泡即可逃出密室。每个灯泡有个权值 Ai,每条边也有个权值 Bi

点亮第 1 个灯泡不需要花费,之后每点亮一个新的灯泡 V 的花费,等于上一个被点亮的灯泡 U 到这个点 V 的距离 D(u,v),乘以这个点的权值 Av

在点灯的过程中,要保证任意时刻所有被点亮的灯泡必须连通,在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡。请告诉他们,逃出密室的最少花费是多少。

1n2×105,1<Ai,Bi105

题解

参考了 zzzzx 大佬的博客

完全二叉树的性质是很优秀的,主要有两点。

  • 树高不超过 log2n
  • 对于每个节点,最多只存在一个兄弟节点。

根据这两条性质我们可以去考虑如何解决。

首先认真读题 ,一开始任选起点,任意时刻点亮的灯泡是联通的,并且子树需要点完。

所以我们点完一个点后,下一步,要么回到它的一个祖先,要么回到它祖先的一个儿子。

由于树高是 O(logn) 的,所以我们

f[i][j] 为考虑完 i 子树的节点,回到它 j 级祖先,所需要的最小花费。

g[i][j] 为考虑完 i 子树的节点,回到它 j 级祖先的另外一个儿子,所需要的最小花费。

为了方便,我们预处理 dis[i][j]ij 级祖先的距离。

规定 anc(i, j)ij 级祖先,bro(i, j)ij 级祖先的另外一个儿子。(这些都可以通过二进制表达)ls 为左儿子,rs 为右儿子。

转移的话,分三种情况讨论。

  1. i 为叶子节点,那么它只能向父亲走,可以直接计算。

    f[i][j] = dis[i][j] * a[anc(i, j)]; g[i][j] = (dis[i][j] + dis[bro(i, j)][1]) * a[bro(i, j)];
  2. i 只有一个儿子。(只可能为左儿子)然后考虑从左儿子直接转移上来即可。

    f[i][j] = f[ls][j + 1] + dis[ls][1] * a[ls]; g[i][j] = g[ls][j + 1] + dis[ls][1] * a[ls];
  3. i 有两个儿子,那直接枚举它走两个儿子的先后顺序就行了。

    f[i][j] = min( g[ls][1] + dis[ls][1] * a[ls] + f[rs][j + 1], g[rs][1] + dis[rs][1] * a[rs] + f[ls][j + 1]); g[i][j] = min( g[ls][1] + dis[ls][1] * a[ls] + g[rs][j + 1], g[rs][1] + dis[rs][1] * a[rs] + g[ls][j + 1]);

最后枚举起点,暴力考虑就行了。

每次只需要一直向上走,然后走上去的时候记得还要走下另外一个儿子,然后要走回来。

具体实现如下:

ll tmp = f[x][1]; for (int u = x >> 1, v = x ^ 1; ; u >>= 1, (v >>= 1) ^= 1) { tmp += (v <= n) ? (dis[v][1] * a[v] + f[v][2]) : dis[u][1] * a[u >> 1]; if (!u) break ; } // tmp 为答案

总结

对于完全二叉树有许多优美的性质可以使用。

并且一定要注意认真读题!!!

代码

总体来说还是比较好写的。

#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 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 DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; typedef long long ll; 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 ("2009.in", "r", stdin); freopen ("2009.out", "w", stdout); #endif } const int N = 2e5 + 1e3; #define anc(x, y) (x >> y) #define bro(x, y) ((x >> y - 1) ^ 1) #define ls (i << 1) #define rs (i << 1 | 1) int a[N], n; ll f[N][20], g[N][20], dis[N][20]; int main () { File(); n = read(); For (i, 1, n) a[i] = read(); For (i, 2, n) { dis[i][1] = read(); for (int j = 2; anc(i, j); ++ j) dis[i][j] = dis[i >> 1][j - 1] + dis[i][1]; } Fordown (i, n, 1) for (int j = 1; ; ++ j) { if (ls > n) { f[i][j] = dis[i][j] * a[anc(i, j)]; g[i][j] = (dis[i][j] + dis[bro(i, j)][1]) * a[bro(i, j)]; } else if (rs > n) { f[i][j] = f[ls][j + 1] + dis[ls][1] * a[ls]; g[i][j] = g[ls][j + 1] + dis[ls][1] * a[ls]; } else { f[i][j] = min( g[ls][1] + dis[ls][1] * a[ls] + f[rs][j + 1], g[rs][1] + dis[rs][1] * a[rs] + f[ls][j + 1]); g[i][j] = min( g[ls][1] + dis[ls][1] * a[ls] + g[rs][j + 1], g[rs][1] + dis[rs][1] * a[rs] + g[ls][j + 1]); } if (!anc(i, j)) break ; } ll ans = 1e18; For (x, 1, n) { ll tmp = f[x][1]; for (int u = x >> 1, v = x ^ 1; ; u >>= 1, (v >>= 1) ^= 1) { tmp += (v <= n) ? (dis[v][1] * a[v] + f[v][2]) : dis[u][1] * a[u >> 1]; if (!u) break ; } chkmin(ans, tmp); } printf ("%lld\n", ans); return 0; }

「SCOI2015」小凸解密码

题意

小凸得到了一个密码盘,密码盘被等分成 N 个扇形,每个扇形上有一个数字(09),和一个符号(+* ),密码盘解密的方法如下:

  1. 首先,选择一个位置开始,顺时针地将数字和符号分别记在数组 A 和数组 C 中;
  2. B0=A0
  3. 当 $ x > 0 x $ 为下标值)时:
  • Cx+Bx=(Ax+Ax1)mod10
  • Cx*Bx=(Ax×Ax1)mod10

操作完成后,可以得到一个长度为 n 的数组 B,然后以 B0 为起点将 B 数组顺时针写成一个环,解密就完成了,称得到的环为答案环。

现在小凸得到了一份指令表,指令表上有 2 种操作。

  • 一种指令是修改操作,即改变原来密码盘上一个位置的数字和符号;
  • 另一种指令是询问操作,具体如下:
    • 首先从指令给出的位置开始完成解密,得到答案环;
    • 答案环上会有一些 0 连在一起,将这些连在一起的 0 称为零区间,找出其中距离 B0 最远的那个零区间,输出这个距离;
    • 零区间和 B0 的距离定义为:零区间内所有 0B0 距离中的最小值。

5n,m105

题解

不难发现每次操作和询问最多只会改变周围两个点的状态,我们每次暴力改变状态就行了。

然后考虑用 std :: set<pair<int, int>> 维护所有全零区间。

每次插入的时候把相邻的合并,删除的时候拿出来拆开就行了。

查询的时候,只需要先查它离它对立面( B0+n2 )最近的三四个区间就行了。(怕少找了就多搞了几个区间)

然后依次考虑距离就行了,最后找最长距离的就行了。

注意它是个环,算距离以及找相邻区间的时候要考虑上首尾区间!!

复杂度是 O((n+m)logn) 的,随机数据跑的很快(全零区间较少)。

代码

细节有点多,建议参考代码。

#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 DEBUG(...) fprintf(stderr, __VA_ARGS__) #define fir first #define sec second #define mp make_pair using namespace std; typedef pair<int, int> PII; 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; } inline char read_opt() { char ch(getchar()); for (; ch != '+' && ch != '*'; ch = getchar()); return ch; } void File() { #ifdef zjp_shadow freopen ("2010.in", "r", stdin); freopen ("2010.out", "w", stdout); #endif } const int N = 1e5 + 1e3; int n; set<PII> S; inline void Insert(int pos) { int l = pos, r = pos; auto nex = S.lower_bound(mp(pos, 0)); if (nex != S.begin()) { auto pre = prev(nex); if (pre != S.end() && pre -> sec == l - 1) l = pre -> fir, S.erase(pre); } if (nex != S.end() && nex -> fir == r + 1) r = nex -> sec, S.erase(nex); S.emplace(l, r); } inline void Delete(int pos) { auto Seg = prev(S.upper_bound(mp(pos, n))); int l = Seg -> fir, r = Seg -> sec; assert(l <= pos && pos <= r); S.erase(Seg); if (l < pos) S.emplace(l, pos - 1); if (r > pos) S.emplace(pos + 1, r); } inline int Len(PII a, int b) { if (a.fir <= b && b <= a.sec) return 0; return min( {abs(a.fir - b), n - abs(a.fir - b), abs(a.sec - b), n - abs(a.sec - b)} ); } #define Dis(a) Len(a, pos) inline int Calc(int pos, PII Seg) { int dis = Dis(Seg); if (Seg.fir == 0) { auto End = prev(S.end()); if (End -> sec == n - 1) chkmin(dis, Dis(*End)); } if (Seg.sec == n - 1) { auto Beg = S.begin(); if (Beg -> fir == 0) chkmin(dis, Dis(*Beg)); } return dis; } inline int Find(int pos) { if (S.empty()) return - 1; int ans = 0; auto cur = S.lower_bound(mp((pos + n / 2) % n, (pos + n / 2) % n)); if (cur == S.end()) cur = prev(cur); if (cur != S.end()) chkmax(ans, Calc(pos, *cur)); auto pre = cur != S.begin() ? prev(cur) : prev(S.end()); chkmax(ans, Calc(pos, *pre)); pre = pre != S.begin() ? prev(pre): prev(S.end()); chkmax(ans, Calc(pos, *pre)); auto nex = next(cur); if (nex == S.end()) nex = S.begin(); chkmax(ans, Calc(pos, *nex)); return ans; } int m; char opt[N]; int val[N], res[N]; inline void Update(int pos, bool flag = false) { if (pos == n) return ; int bef = res[pos]; if (flag) res[pos] = val[pos]; else { if (opt[pos] == '+') res[pos] = (val[pos] + val[(pos - 1 + n) % n]) % 10; if (opt[pos] == '*') res[pos] = (val[pos] * val[(pos - 1 + n) % n]) % 10; } if (~bef) { if (!bef && res[pos]) Delete(pos); if (bef && !res[pos]) Insert(pos); } else if (!res[pos]) Insert(pos); } int main () { File(); n = read(); m = read(); Set(res, -1); Rep(i, n) val[i] = read(), opt[i] = read_opt(), Update(i, !i); Rep(i, m) { int type = read(), pos = read(); if (type == 1) { val[pos] = read(), opt[pos] = read_opt(); Update(pos); Update(pos + 1); } else { if (pos) Update(0, false), Update(pos, true); printf ("%d\n", Find(pos)); if (pos) Update(pos, false), Update(0, true); } } return 0; }

「SCOI2015」情报传递

题意

奈特公司是一个巨大的情报公司,它有着庞大的情报网络。情报网络中共有 n 名情报员。每名情报员可能有若干名(可能没有)下线,除 1 名大头目外其余 n1 名情报员有且仅有 1 名上线。奈特公司纪律森严,每名情报员只能与自己的上、下线联系,同时,情报网络中仟意两名情报员一定能够通过情报网络传递情报。

奈特公司每天会派发以下两种任务中的一个任务:

  1. 搜集情报:指派 T 号情报员搜集情报;
  2. 传递情报:将一条情报从 X 号情报员传递给 Y 号情报员。

情报员最初处于潜伏阶段,他们是相对安全的,我们认为此时所有情报员的危险值为 0;一旦某个情报员开始搜集情报,他的危险值就会持续增加,每天增加 1 点危险值(开始搜集情报的当天危险值仍为 0,第 2 天危险值为 1,第 3 天危险值为 2,以此类推)。传递情报并不会使情报员的危险值增加。

为了保证传递情报的过程相对安全,每条情报都有一个风险控制值 C。余特公司认为,参与传递这条情报的所有情报员中,危险值大于 C 的情报员将对该条情报构成威胁。现在,奈特公司希望知道,对于每个传递情报任务,参与传递的情报员有多少个,其中对该条情报构成威胁的情报员有多少个。

n2×105,Q2×105,0<Pi,CiN,1Ti,Xi,Yin

题解

很简单的一道题。

第一问就是求树上两点距离,十分思博。(用倍增求就行了,比较好写,我竟然写挂了

第二问就是求在一个时刻 tim 前树上路径上的关键点数。

由于它没有强制在线,离线显然更好做。我们把询问和修改都离线到时间上。

问题就转化成树上,单点加链查询,这个可以转化成子树加单点查的经典操作,不会可以看这里

我们有树状数组维护区间加,单点查就行了。复杂度就是 O(nlogn) 的,常数比较小。

代码

#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 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 DEBUG(...) fprintf(stderr, __VA_ARGS__) #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 ("2011.in", "r", stdin); freopen ("2011.out", "w", stdout); #endif } const int N = 2e5 + 1e3; vector<int> G[N]; int ans1[N], ans2[N], n, rt; int anc[N][21], dep[N], dfn[N], efn[N]; void Dfs_Init(int u) { static int clk = 0; if (u) dfn[u] = ++ clk; for (int v : G[u]) dep[v] = dep[anc[v][0] = u] + 1, Dfs_Init(v); efn[u] = clk; } #define lowbit(x) (x & -x) template<int Maxn> struct Fenwick_Tree { int sumv[Maxn]; inline void Update(int pos, int uv) { for (; pos <= n; pos += lowbit(pos)) sumv[pos] += uv; } inline void Update(int l, int r, int uv) { Update(l, uv); Update(r + 1, - uv); } inline int Query(int pos) { int res = 0; for (; pos; pos -= lowbit(pos)) res += sumv[pos]; return res; } }; Fenwick_Tree<N> T; struct Query { int x, y, id; }; int tot = 0, Up[N], lim; vector<Query> Q[N]; inline int Lca(int x, int y) { if (dep[x] < dep[y]) swap(x, y); int gap = dep[x] - dep[y]; Fordown (i, lim, 0) if (gap >> i & 1) x = anc[x][i]; if (x == y) return x; Fordown (i, lim, 0) if (anc[x][i] != anc[y][i]) x = anc[x][i], y = anc[y][i]; return anc[x][0]; } int main () { File(); n = read(); For (i, 1, n) G[read()].pb(i); Dfs_Init(0); lim = floor(log2(n)); For (j, 1, lim) For (i, 1, n) anc[i][j] = anc[anc[i][j - 1]][j - 1]; int q = read(); For (tim, 1, q) { int opt = read(); if (opt == 2) Up[tim] = read(); else { int x = read(), y = read(), c = read() + 1, lca = Lca(x, y); ans1[++ tot] = dep[x] + dep[y] - dep[lca] - dep[anc[lca][0]]; if (tim > c) Q[tim - c].pb((Query) {x, y, tot}); } } For (i, 1, q) { int u = Up[i]; if (u) T.Update(dfn[u], efn[u], 1); for (auto cur : Q[i]) { int x = cur.x, y = cur.y, id = cur.id, lca = Lca(x, y); ans2[id] = T.Query(dfn[x]) + T.Query(dfn[y]) - T.Query(dfn[lca]) - T.Query(dfn[anc[lca][0]]); } } For (i, 1, tot) printf ("%d %d\n", ans1[i], ans2[i]); return 0; }

__EOF__

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