zoukankan      html  css  js  c++  java
  • SCOI 2015 Day2 简要题解

    「SCOI2015」小凸玩密室

    题意

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

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

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

    $ 1 leq n leq 2 imes 10 ^ 5, 1 < A_i, B_i leq 10 ^ 5 $

    题解

    参考了 zzzzx 大佬的博客

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

    • 树高不超过 (lfloor log_2 n floor)
    • 对于每个节点,最多只存在一个兄弟节点。

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

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

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

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

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

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

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

    规定 anc(i, j)(i)(j) 级祖先,bro(i, j)(i)(j) 级祖先的另外一个儿子。(这些都可以通过二进制表达)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
    ", ans);
    
    	return 0;
    
    }
    

    「SCOI2015」小凸解密码

    题意

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

    1. 首先,选择一个位置开始,顺时针地将数字和符号分别记在数组 $ A $ 和数组 $ C $ 中;
    2. $ B_0 = A_0 $
    3. 当 $ x > 0 (() x $ 为下标值)时:
    • 若 $ C_x $ 为 +,$ B_x = (A_x + A_{x - 1}) mod 10 $;
    • 若 $ C_x $ 为 *,$ B_x = (A_x imes A_{x - 1}) mod 10 $;

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

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

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

    $ 5 leq n, m leq 10 ^ 5 $

    题解

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

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

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

    查询的时候,只需要先查它离它对立面( (displaystyle B_0 + frac{n}{2}) )最近的三四个区间就行了。(怕少找了就多搞了几个区间)

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

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

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

    代码

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

    #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
    ", Find(pos));
    			if (pos) Update(pos, false), Update(0, true);
    		}
    	}
    
    	return 0;
    
    }
    

    「SCOI2015」情报传递

    题意

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

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

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

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

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

    $ n leq 2 imes 10 ^ 5, Q leq 2 imes 10 ^ 5, 0 < P_i, C_i leq N, 1 leq T_i, X_i, Y_i leq n $

    题解

    很简单的一道题。

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

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

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

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

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

    代码

    #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
    ", ans1[i], ans2[i]);
    
    	return 0;
    
    }
    
  • 相关阅读:
    Executors源码之线程池
    Java序列化对字段名的影响
    Spring Cloud Alibaba(二)
    Security版本冲突,老版本共用服务接入新版本服务
    记一次虚拟机崩溃事件和解决方法(CentOS7)
    vue-cli 项目构建学习笔记(Vue3)
    IDEA插件-IDE Eval Reset
    Docker的学习
    Spring Security的学习
    Spring MVC框架的设计理念
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10024716.html
Copyright © 2011-2022 走看看