2025.1.17 CW 模拟赛

题面 & 题解

T1

思路

通过打表可以发现, 当我们在最前方放 \(k - 1\)\(1\), 再在后面一堆 \(0\) 的中间放一个 \(1\) 一定是最优的.

严格证明一下. 将数组做前缀和, 令前缀和中 \(1\) 的个数为 \(X\), \(0\) 的个数为 \(Y\), 那么答案即为 \(X \times Y\), 又因为 \(X + Y = n + 1\), 所以只需要使 \(X, Y\) 尽可能靠近, 如上段所说的构造方法一定能够做到 \(X \times Y\) 取到最大.

T2

算法

数据结构, 线段树.

思路

注意到题目中所说的保证任意两个区间都是不相交或者是包含的关系, 那么其实我们就可以将其看作一个树形的关系, 一个区间的父亲必须在这个区间之后满足要求. 同时, 每一个叶子节点都互不相交, 相当于每一个点都只会至多出现在一个区间中, 每次修改后查询这个区间是否合法即可.

实现

一步一步来分析, 先来看怎么维护一个区间内部颜色是否互不相同.

我们可以维护一个 \(lst_i\) 表示 \(i\) 之前第一个与之颜色相同的点, 那么原问题等价于 \(\displaystyle \max_{i = l}^r lst_i < l\). 同时我们再记录一个 \(pre_i\)​ 表示后面第一个与之颜色相同的点, \(st_i\) 表示颜色为 \(i\) 的点的下标的集合.

for (int i = 1; i <= n; ++i) {
	lst[i] = pre[c[i]];
	nxt[pre[c[i]]] = i;
	pre[c[i]] = i;
	st[c[i]].insert(i);
}

至于查询 \(\displaystyle \max_{i = l}^r lst_i < l\), 使用线段树即可.

// 单点修改, 区间查询最大值
class Segment_Tree {
private:
	int P, tr[N * 3];
	
public:
	void build() {
		P = 1;
		while (P <= n + 1) P <<= 1;
		for (int i = 1; i <= n; ++i) tr[P + i] = lst[i];
		for (int i = P - 1; i; --i) tr[i] = max(tr[i << 1], tr[i << 1 | 1]);
	}
	
	void update(int x, int k) {
		tr[x += P] = k;
		for (x >>= 1; x; x >>= 1) tr[x] = max(tr[x << 1], tr[x << 1 | 1]);
	}
	
	int query(int l, int r) {
		int ans = 0;
		for (l += P - 1, r += P + 1; l ^ 1 ^ r; l >>= 1, r >>= 1) {
			if (~l & 1) ans = max(ans, tr[l ^ 1]);
			if (r & 1) ans = max(ans, tr[r ^ 1]);
		}
		return ans;
	}
	
} ST;

接下来考虑如何建立树形关系. 显然, 我们可以对所有区间按左端点升序, 右端点降序排列. 就直接用一个栈维护其父亲 (因为已经排好序了). \(rd\) 是指当前点的入度, 也就是儿子个数 (后面要用).

for (int i = 1, top = 0; i <= m; ++i) {
	if (p[i].l == p[i].r or ST.query(p[i].l, p[i].r) < p[i].l) {
		ans[p[i].id] = 0;
		rd[p[i].id] = -1;
		continue;
	}
	pos[p[i].id] = i;
	while (top and stk[top].r < p[i].l) --top;
	++rd[fa[p[i].id] = stk[top].id];
	stk[++top] = p[i];
}

在初始的时候, 我们只关心叶子结点 (如果叶子结点都不合法, 那么其父亲一定不合法), 也就是只插入入度为 \(0\) 的点. 再在后面操作的时候顺便更新即可. 所以我们的思路其实是从底自上不断地判断是否合法, 再进行合并.

void check(int x) {
	if (ST.query(l[x], r[x]) < l[x]) {
		s.erase({l[x], r[x], x});
		ans[x] = t;
		if (fa[x] and !(--rd[fa[x]])) {
			rd[fa[x]] = -1;
			s.insert(p[pos[fa[x]]]);
			check(fa[x]);
		}
	}
}

最后再来思考修改操作. 这一部分不是很难, 就直接给代码了.

void modify(int x, int y) {
	if (nxt[x]) ST.update(nxt[x], lst[x]);
	nxt[lst[x]] = nxt[x];
	lst[nxt[x]] = lst[x];
	st[c[x]].erase(x);
	c[x] = y;
	int p = lst[x];
	auto it = st[y].upper_bound(x);
	lst[x] = nxt[x] = 0;
	if (it != st[y].end()) {
		nxt[x] = *it;
		ST.update(*it, x);
	}
	if (it != st[y].begin()) lst[x] = *(--it);
	st[y].insert(x);
	lst[nxt[x]] = nxt[lst[x]] = x;
	if (p ^ lst[x]) ST.update(x, lst[x]);
}

完整代码

T3

原题链接: [ARC108F] Paint Tree

算法

树论, 组合数学.

思路

先抛一个一个树的性质: 对于一棵树, 到一个点最远的点一定是直径之一.

于是我们先找出直径的两个端点 \(x, y\). 若 \(x, y\) 颜色相同, 则最长距离即为直径.

考虑 \(x, y\) 颜色不同的情况. 不妨设 \(x\) 为白色, \(y\) 为黑色, 我们从大到小枚举答案 \(d\).

那么所有到 \(x\) 距离 \(> d\) 的点必然为白色, 所有到 \(y\) 距离 \(> d\) 的点必然为黑色.

如果一个点到 \(x, y\) 的距离均 \(\le d\), 那么两种颜色均可.

考虑什么时候终止枚举. 显然, 如果 \(\displaystyle \exist i, s.t. \min(dis_{i, x}, dis_{i, y}) > d\) 那么答案一定不合法, 直接 \(\rm{break}\) 掉即可.

#include "iostream"
#include "algorithm"

using namespace std;

#define int long long

namespace Fast_IO {
	void read() {}
	template <class T, class ...T1>
	void read(T &x, T1 &...y) {
		x = 0;
		char ch = getchar(); bool f = 1;
		for (; ch < '0' or ch > '9'; ch = getchar()) if (ch == '-') f = 0;
		for (; ch >= '0' and ch <= '9'; x = x * 10 + (ch & 15), ch = getchar());
		x = (f ? x : -x);
		read(y...);
	}
	
	void print(int x) {
		if (x < 0) putchar('-'), x = -x;
		if (x > 9) print(x / 10);
		putchar(x % 10 + '0');
	}
} using namespace Fast_IO;

constexpr int N = 1e6 + 10, mod = 1e9 + 7;

int n, x, y, mx, pw[N];
basic_string<int> e[N];

void dfs(int u, int fa, int l, bool f) {
	if (l > mx) (f ? y : x) = u, mx = l;
	for (int v : e[u]) if (v ^ fa) dfs(v, u, l + 1, f);
}

void init() {
	read(n);
	pw[0] = 1;
	for (int i = 1; i <= n; ++i) pw[i] = pw[i - 1] * 2 % mod;
	for (int i = 1, u, v; i ^ n; ++i) {
		read(u, v);
		e[u].push_back(v), e[v].push_back(u);
	}
	mx = -1, dfs(1, 0, 0, 0);
	mx = -1, dfs(x, 0, 0, 1);
}

int dis[2][N], mxdis[N], id[N];

void deal(int u, int fa, int l, int f) {
	dis[f][u] = l;
	for (int v : e[u]) if (v ^ fa) deal(v, u, l + 1, f);
}

void calculate() {
	deal(x, 0, 0, 0), deal(y, 0, 0, 1);
	int mn = -1;
	for (int i = 1; i <= n; ++i)
		mn = max(mn, min(dis[0][i], dis[1][i])), mxdis[i] = max(dis[0][i], dis[1][i]);
	sort(mxdis + 1, mxdis + n + 1, greater<>());
	int ans = mx * pw[n] % mod;
	for (int i = mx - 1, p = 1; i >= mn; --i) {
		while (p <= n and mxdis[p] > i) ++p;
		ans = (ans - pw[n - p + 2] + mod) % mod;
	}
	print(ans);
}

void solve() {
	init();
	calculate();
}

signed main() {
	solve();
	return 0;
}
posted @ 2025-01-17 21:07  Steven1013  阅读(17)  评论(1)    收藏  举报