SCOI 2015 Day2 简要题解

「SCOI2015」小凸玩密室

题意

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

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

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

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

题解

参考了 zzzzx 大佬的博客

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

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

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

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

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

由于树高是 \(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\n", 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}) \bmod 10 $;
  • 若 $ C_x $ 为 *,$ B_x = (A_x \times A_{x - 1}) \bmod 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\n", 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 \times 10 ^ 5, Q \leq 2 \times 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\n", ans1[i], ans2[i]);

	return 0;

}
posted @ 2018-11-27 10:03  zjp_shadow  阅读(220)  评论(0编辑  收藏  举报