240106 杂题选谈

发布时间 2024-01-07 14:32:19作者: XSC062

想不到好标题了。

有句话怎么说来着,罗马不是一天建成的,是一天天建成的。

还有什么,Do in Rome as the Romans' do,还有一句,All roads leads to Rome。


A. 连续的零 zero

http://222.180.160.110:1024/contest/4647/problem/1

做个前缀和,看看任意一个长度为 \(k\) 的区间中有几个 \(1\)。复杂度 \(\mathcal O(n)\)

namespace XSC062 {
using namespace fastIO;
const int maxn = 5e5 + 5;
const int inf = 0x3f3f3f3f;
int n, m, res = inf;
int a[maxn], s[maxn];
int min(int x, int y) {
	return x < y ? x : y;
}
int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; ++i) {
		scanf("%1d", &a[i]);
		s[i] = s[i - 1] + a[i];
		if (i >= m)
			res = min(res, s[i] - s[i - m]);
	}
	print(res, '\n');
	return 0;
}
} // namespace XSC062

B. 反回文串 anti

http://222.180.160.110:1024/contest/4647/problem/2

\(n\) 为奇时,中间的元素一定和自己相等,故无解。

当数量最多的一个字符个数超过 \(\dfrac n2\) 时,由鸽巢得无解。

剩下的情况一定有解。

证明

可以找到一种合法的构造方式。我们列出一个列数为 \(2\),行数为 \(\dfrac n2\) 的表格,将所有相同字母排列在一起,按照从左到右,从上到下的方式将字母填入表格,最后将第 \(i\) 行第一列的字母填入 \(a_i\),第 \(i\) 行第二列的字母填入 \(a_{n-i+1}\),即可完成构造。

一种字母只在第一列或第二列出现当然合法,如果从第一列持续到第二列,因为任意字母出现次数不超过 \(\dfrac n2\) 次,所以同一行的两列不会出现同一种字母。

这叫什么,有字证明。

感觉可以拿去出一道类似于「找到字符串字典序最小的反回文串」之类的小水题

然后现在我们知道有解了,怎么找到最优解呢。

比如有一组 \((a_i,a_{n-i+1})=(\texttt a, \texttt a)\),还有一组 \((a_j, a_{n-j+1})=(\texttt b, \texttt b)\),那我们直接把 \(a_i\)\(a_j\) 交换,皆大欢喜。

这就说明我们需要把值不相等的非法 \(a_i\)\(a_j\) 配对。

然后我们就可以沿用证明中的构造方式,分组配对,一定最优,每组代价为 \(1\)

同一行两个值 \(v\) 相等,因为已经最优了,所以不能再在非法串中寻找答案。应该找合法对中某个值交换,每组代价为 \(2\)。具体和谁交换我们不用担心,只要找到一组 \((a_i,a_{n-i+1})\) 满足 \(a_i\ne v\)\(a_{n-i+1}\ne v\) 就可以了,然后我们又知道 \(v\) 的个数 \(\ne \dfrac n2\),假设 \(\dfrac n2\) 对中每队都有至少一个 \(v\),由于当前这一对有两个相同的 \(v\),那么 \(v\) 的个数就会大于 \(\dfrac n2\),矛盾了,所以一定能找到。

对于非法总对数是奇数的情况,我们要钦定一个非法对强制让其和合法对交换,贪心一下取非法对数量最多的 \(v\) 的某一对最优。

namespace XSC062 {
using namespace fastIO;
const int maxm = 35;
const int maxn = 2e5 + 5;
char s[maxn];
int cnt[maxm], p[maxn];
int T, n, tot, res, now;
int main() {
	scanf("%d", &T);
	while (T--) {
		tot = 0;
		scanf("%d %s", &n, s + 1);
		if (n & 1) {
			puts("-1");
			continue;
		}
		memset(cnt, 0, sizeof (cnt));
		for (int i = 1; i <= n; ++i)
			++cnt[s[i] - 'a' + 1];
		for (int i = 1; i <= 26; ++i) {
			if (cnt[i] * 2 > n) {
				puts("-1");
				goto noSol;
			}
		}
		memset(cnt, 0, sizeof (cnt));
		for (int i = 1; i * 2 <= n; ++i) {
			if (s[i] == s[n - i + 1])
				++cnt[s[i] - 'a' + 1], ++tot;
		}
		std::sort(cnt + 1, cnt + 27,
					std::greater<int>());
		res = now = 0;
		if (tot & 1) {
			res = 1, --cnt[1];
			std::sort(cnt + 1, cnt + 27,
						std::greater<int>());
		}
		for (int i = 1; i <= 26; ++i) {
			while (cnt[i]--) {
				if (++now > tot / 2) {
					if (i == p[now - tot / 2])
						res += 2;
					else ++res;
				}
				else p[now] = i;
			}
		}
		print(res, '\n');
		noSol: ;
	}
	return 0;
}
} // namespace XSC062

C. 除与减 divsub

http://222.180.160.110:1024/contest/4647/problem/3

小数学,还好。

假设 \(n=d\times k^p\),其中 \(k\nmid d\),那么我们分两种情况讨论。

  1. \(p=0\),即 \(k\nmid n\),那么 \(n\bmod k=1\),即 \(k\mid (n-1)\)

    这个时候问 \(k\) 的个数就相当于是在问 \(n-1\)\(1\) 以外的因子个数。假设 \(n-1={x_1}^{p_1}{x_2}^{p_2}\cdots {x_m}^{p_m}\),那么答案为 \((\prod p_i+1)-1\),减去的是 \(1\)

  2. \(p\ne 0\)\(k\mid n\)

    这个时候好像并没有什么好的转化。好消息是 \(n\) 的范围是 \(10^{12}\),根号枚举因数复杂度跑得过。所以我们就可以暴力判定 \(n\) 的所有因数是否满足条件。

时间复杂度,\(\mathcal O(\sqrt n\times \log n)\),枚举因数是根号,算次数(也就是算 \(d\))是 \(\log\)

#define int long long
namespace XSC062 {
using namespace fastIO;
int n, m, res, cnt;
int main() {
	read(n), m = n;
	for (int i = 2; i * i <= n; ++i) {
		if (n % i == 0) {
			m = n;
			while (m % i == 0) m /= i;
			if (m % i == 1) ++res;
			if (i * i != n) {
				m = n;
				while (m % (n / i) == 0)
					m /= (n / i);
				if (m % (n / i) == 1) ++res;
			}
		}
	}
	m = n - 1, cnt = 1;
	for (int i = 2; i * i <= m; ++i) {
		if (m % i == 0) {
			int now = 0;
			while (m % i == 0)
				++now, m /= i;
			cnt *= now + 1;
		}
	}
	if (m != 1) cnt *= 2;
	print(res + cnt, '\n');
	return 0;
}
} // namespace XSC062
#undef int

D. 图书管理员 librarian

http://222.180.160.110:1024/contest/4647/problem/4

[SDOI2008] 郁闷的小 J。

关于这个,我们发现自己不会考场现冲主席树。哎,打 CDQ 又怕写挂。

我们发现这道题的修改都是单点的,询问也只关于某一种颜色,不同的颜色之间没有影响。

于是我们可以把操作离线下来,初始视作将某颜色在某位置增加,修改视作将某颜色在某位置删除,将另一颜色在该位置增加,将所有操作按颜色离散化分类然后 vector 下来,对于所有颜色从前到后树状数组做一遍操作就能 \(\mathcal O(n\log n+q\log n)\) 解决。

树状数组清空是肯定不能 memset 的,复杂度不对。那么怎么办呢?把所有操作撤回去就可以了。

namespace XSC062 {
using namespace fastIO;
const int maxn = 2e5 + 5;
struct __ {
	int ty, l, r, v;
	__() {}
	__(int t1, int l1, int r1, int v1 = 0) {
		if (t1 == 0)
			ty = 0, l = l1, v = r1;
		else ty = 1, l = l1, r = r1, v = v1;
	}
};
char ty;
std::map<int, int> tab;
std::vector<__> q[maxn];
int n, m, tot, x, y, v, id;
int Bit[maxn], a[maxn], res[maxn];
int lowbit(int x) { return x & -x; }
void add(int x, int v) {
	for (; x <= n; x += lowbit(x))
		Bit[x] += v;
	return;
}
int ask(int x) {
	int res = 0;
	for (; x; x -= lowbit(x)) res += Bit[x];
	return res;
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; ++i) {
		read(a[i]);
		if (!tab.count(a[i]))
			tab[a[i]] = ++tot;
		a[i] = tab[a[i]];
		q[a[i]].emplace_back(0, i, 1);
	}
	while (m--) {
		scanf("%1s", &ty);
		if (ty == 'C') {
			read(x), read(y);
			if (!tab.count(y))
				tab[y] = ++tot;
			y = tab[y];
			q[a[x]].emplace_back(0, x, -1);
			q[a[x] = y].emplace_back(0, x, 1);
		}
		else {
			++id;
			read(x), read(y), read(v);
			if (!tab.count(v)) continue;
			v = tab[v];
			q[v].emplace_back(1, x, y, id);
		}
	}
	for (int i = 1; i <= tot; ++i) {
		for (auto &j : q[i]) {
			if (j.ty == 0) add(j.l, j.v);
			else {
				res[j.v] =
					ask(j.r) - ask(j.l - 1);
			}
		}
		for (auto &j : q[i])
			if (j.ty == 0) add(j.l, -j.v);
	}
	for (int i = 1; i <= id; ++i)
		print(res[i], '\n');
	return 0;
}
} // namespace XSC062

E 会单独开一篇。


F. 树 tree

http://222.180.160.110:1024/contest/4647/problem/6

CF916E。

大分讨给我整不会了,更给我整不会的是下来过后发现这只是个小分讨。

更新子树和子树查询我们都会。换根 DP 我们也都写过,都知道换根并不会对子树结构产生大的影响。所以应当是能根据已知信息推测出子树在原树上对应的点集的。

\(r\) 为当前树根,\(\text {LCA}(x,y)\)\(x,y\)\(1\) 为根时的 LCA,\(\text {LCA}'(x,y)\) 表示 \(x,y\)\(r\) 为根时的 LCA。

那么对于 \(\text {LCA}'(x,y)\),肯定是要讨论 \(x,y\)\(r\) 的位置关系的。

  1. \(\text {LCA}(x,y)\)\(r\) 的子孙。此时 \(\text {LCA}'(x,y) = \text {LCA}(x,y)\)
  2. \(\text {LCA}(x,y)\)\(r\) 的祖先。那么说明至少有一个点不是 \(r\) 的子孙。此时 \(\text {LCA}(x,y)'\) 的值为 \(r\) 为另一个点的 LCA。

整理可得 \(\text {LCA}'(x,y)\)\(\text {LCA}(x,y)\)\(\text {LCA}(x,r)\)\(\text {LCA}(y,r)\) 中的深度最大者。

对于以 \(r\) 为根时的子树 \(x\)

  1. \(x=r\),此时子树为整棵树。
  2. \(\text {LCA}(x,r)\ne x\),即 \(r\) 不为 \(x\) 的子孙,此时子树就是以 \(1\) 为根是的子树 \(x\)
  3. \(\text {LCA}(x,y)=x\),即 \(r\)\(x\) 的子孙,此时子树是整棵树除开 \(x\) 包含 \(r\) 的儿子及其子孙。修改和查询的时候容斥一下就好。这个时候的子树倍增跳一下就能找到。

然后就是常规线段树维护了。时间复杂度 \(\mathcal O(n\log n)\)

namespace XSC062 {
using namespace fastIO;
const int maxm = 35;
const int maxn = 1e5 + 5;
#define lt (p << 1)
#define rt (lt | 1)
struct _ { int l, r, u, d; };
_ t[maxn << 2];
int f[maxn][maxm];
std::vector<int> g[maxn];
int a[maxn], dfn[maxn], rfn[maxn];
int n, q, r, ty, x, y, v, si, now;
int top[maxn], dep[maxn], tab[maxn];
void swap(int &x, int &y) {
	x ^= y ^= x ^= y;
	return;
}
void DFS(int x) {
	dep[x] = dep[f[x][0]] + 1;
	dfn[x] = ++now, tab[now] = x;
	for (auto i : g[x]) {
		if (i == f[x][0]) continue;
		f[i][0] = x;
		for (int j = 1; j <= si; ++j)
			f[i][j] = f[f[i][j - 1]][j - 1];
		DFS(i);
	}
	rfn[x] = now;
	return;
}
void pushup(int p) {
	t[p].u = t[lt].u + t[rt].u;
	return;
}
void pushdown(int p) {
	if (t[p].d) {
		t[lt].d += t[p].d;
		t[rt].d += t[p].d;
		t[lt].u += t[p].d *
				(t[lt].r - t[lt].l + 1);
		t[rt].u += t[p].d *
				(t[rt].r - t[rt].l + 1);
		t[p].d = 0;
	}
	return;
}
void bld(int p, int l, int r) {
	t[p].l = l, t[p].r = r;
	if (l == r) {
		t[p].u = a[tab[l]];
		return;
	}
	int mid = (l + r) >> 1;
	bld(lt, l, mid), bld(rt, mid + 1, r);
	pushup(p);
	return;
}
void add(int p, int x, int v) {
	t[p].u += v;
	if (t[p].l == t[p].r) return;
	pushdown(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (x <= mid) add(lt, x, v);
	else add(rt, x, v);
	return;
}
void add(int p, int l, int r, int v) {
	if (l <= t[p].l && t[p].r <= r) {
		t[p].d += v;
		t[p].u += (t[p].r - t[p].l + 1) * v;
		return;
	}
	pushdown(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) add(lt, l, r, v);
	if (r > mid) add(rt, l, r, v);
	pushup(p);
	return;
}
int ask(int p, int l, int r) {
	if (l <= t[p].l && t[p].r <= r)
		return t[p].u;
	pushdown(p);
	int res = 0,
		mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) res = ask(lt, l, r);
	if (r > mid) res += ask(rt, l, r);
	return res;
}
int LCA(int x, int y) {
	if (dep[x] < dep[y]) swap(x, y);
	for (int i = si; ~i; --i) {
		if (dep[f[x][i]] >= dep[y])
			x = f[x][i];
	}
	if (x == y) return x;
	for (int i = si; ~i; --i) {
		if (f[x][i] != f[y][i])
			x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}
void Add(int x, int v) {
	int rlca = LCA(r, x);
	if (x == r) add(1, 1, n, v);
	else if (rlca != x)
		add(1, dfn[x], rfn[x], v);
	else {
		add(1, 1, n, v);
		int p = r;
		for (int i = si; ~i; --i) {
			if (dep[f[p][i]] >= dep[x] + 1)
				p = f[p][i];
		}
		add(1, dfn[p], rfn[p], -v);
	}
	return;
}
void tAdd(int x, int y, int v) {
	int llca = LCA(r, x), rlca = LCA(r, y),
		ulca = LCA(x, y);
	if (dep[llca] >= dep[rlca] &&
		dep[llca] >= dep[ulca]) Add(llca, v);
	else if (dep[rlca] >= dep[llca] &&
		dep[rlca] >= dep[ulca]) Add(rlca, v);
	else Add(ulca, v);
	return;
}
int tAsk(int x) {
	int rlca = LCA(r, x);
	if (x == r) return t[1].u;
	if (rlca != x)
		return ask(1, dfn[x], rfn[x]);
	int p = r;
	for (int i = si; ~i; --i) {
		if (dep[f[p][i]] >= dep[x] + 1)
			p = f[p][i];
	}
	return t[1].u - ask(1, dfn[p], rfn[p]);
}
void add(int x, int y) {
	g[x].push_back(y);
	return;
}
int main() {
	read(n), read(q), r = 1;
	si = log(n) / log(2.0);
	for (int i = 1; i <= n; ++i) read(a[i]);
	for (int i = 1; i < n; ++i) {
		read(x), read(y);
		add(x, y), add(y, x);
	}
	dep[0] = 1, DFS(1), dep[0] = 0;
	bld(1, 1, n);
	while (q--) {
		read(ty);
		if (ty == 1) read(r);
		else if (ty == 2) {
			read(x), read(y), read(v);
			tAdd(x, y, v);
		}
		else {
			read(x);
			print(tAsk(x), '\n');
		}
	}
	return 0;
}
} // namespace XSC062
#undef int