[CF 1158E] Strange device

题目大意

这是一道交互题,你需要猜出一个 \(n\) 个点的树。

你可以作出 \(\leq 80\) 次如下询问:

给交互库一个序列 \(d_1,d_2, \ldots, d_n\),交互库会返回一个 01 串,表示对于每一个节点 \(i\) 是否存在节点 \(j\ (i \neq j)\) 使得 \(dis(i,j) \leq d_j\)

\(2 \leq n \leq 1000\)

题解

遇到这种题一般的套路是先确定每个点在整个树中的位置然后再具体确定父子关系,因为往往你能做的操作只能得出祖先关系。

所以我们先考虑怎么求出一个点的深度。

如何求出深度

可以考虑一个分治的过程,先假定 \(1\) 为根,会好做一点。

可以先求出所有距离 \(1\) 大于 \(\frac{n}{2}\) 的点,等于 \(\frac{n}{2}\) 的点,小于 \(\frac{n}{2}\) 的点。

具体的,我们做两次询问,第一次 \(d_1=\frac{n}{2}\) 其余为 \(0\),第二次 \(d_1=\frac {n}{2} - 1\) 其余为 \(0\)

对这个做法推而广之,对于每个子区间我们都可以这样对其求出具体的一个新的深度的点击,并且将其分为两个本质相同的子问题。

但是我们这样对题目的条件使用率太低了,也无法完成他题目中询问次数的限制。

于是我们考虑将同一层的询问合并。

由于对于一个点询问可能会影响到上方的点,又因为每次询问的距离大约为一半的区间长度,所以我们可以将奇数下标的区间合并到一个询问,将偶数下表的区间合并到一个询问,这样每个询问之间就不会互相影响了。

如何求出父子关系

考虑拆位,如果我们将深度为 \(d\) 的第 \(k\) 位为 \(1\) 的点都设为 \(1\) 其余设为 \(0\),那么深度为 \(d+1\) 的点在结果中的值就是其父亲第 \(k\) 位的值。

正确性显然。

且这种询问方法只影响到 \(d - 1,d,d + 1\) 深度的点,所以我们可以将所有 \(\mod{3}\) 同余的点塞入同一个询问中。

总结

求出深度需要用 \(4 \log n\) 次询问,求出父子关系需要用 \(3 \log n\) 次询问。

总的需要 \(7 \log n\) 次询问,时间复杂度为 \(\mathcal{O}(n \log n)\)

代码

码风比较奔放,谅解一下。

#include <cstdio>
#include <algorithm>
#include <vector>
#include <numeric>
#include <cassert>
using namespace std;

const int N = 1010;
int n, head[N], nxt[N * 2], to[N * 2], tot, fa[N];
void add(int x, int y) { nxt[++ tot] = head[x]; head[x] = tot, to[tot] = y; }
char s[N * 10];

bool dfs(int x, int fa, int dis, const vector<int> &curd) {
	if (dis <= curd[x] && curd[x] > 0) return true;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (y != fa && dfs(y, x, dis + 1, curd)) return true;
	} 
	return false;
}

vector<int> interact(vector<int> d) {
	assert(d.size() == n + 1);

	static int ask_count = 0;

	++ ask_count;

	assert(ask_count <= 80);

	vector<int> ret(n + 1);
	printf("? ");
	for (int i = 1; i <= n; i ++) printf("%d%c", d[i], " \n"[i == n]);
	fflush(stdout);

#ifdef LOCAL
	for (int i = 1; i <= n; i ++) ret[i] = dfs(i, i, 0, d);
	printf("ask count %d\n", ask_count);
	for (int i = 1; i <= n; i ++) printf("%d%c", ret[i], " \n"[i == n]);
#else
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i ++) ret[i] = s[i] - '0';
#endif

	return ret;
}

void init_local() {
	for (int i = 1, x, y; i < n; i ++) scanf("%d%d", &x, &y), add(x, y), add(y, x);
}

struct Node {
	int l, r;
	vector<int> in;
};

vector<Node> t, nxtt;
vector<int> d[N];
int dep[N];

void solve() {
	vector<int> vec(n - 1);
	iota(vec.begin(), vec.end(), 2);
	t.push_back({ 0, n, vec });
	d[0].push_back(1);
	while (true) {
		sort(t.begin(), t.end(), [&](auto p1, auto p2) { return p1.l < p2.l; });

		nxtt.clear();

		auto push_back = [&](Node x) { if (x.r - x.l + 1 > 2) nxtt.push_back(x); };

		vector<int> q[2];
		q[0].resize(n + 1); q[1].resize(n + 1);
		for (int i = 0; i < t.size(); i ++) {
			int mid = (t[i].l + t[i].r) >> 1;
			for (int x : d[t[i].l]) q[i % 2][x] = mid - t[i].l;
		}

		vector<int> res[2][2];
		for (int type : { 0, 1 }) {
			res[type][0] = interact(q[type]);
			for (int &k : q[type]) if (k) -- k;
			res[type][1] = interact(q[type]);
		}

		for (int i = 0; i < t.size(); i ++) {
			int mid = (t[i].l + t[i].r) >> 1;
			vector<int> left, right;
			for (int x : t[i].in) {
				if (res[i % 2][0][x] && !res[i % 2][1][x]) {
					d[mid].push_back(x);
				} else if (res[i % 2][0][x] && res[i % 2][1][x]) {
					left.push_back(x);
				} else {
					assert(!res[i % 2][0][x] && !res[i % 2][1][x]);
					right.push_back(x);
				}
			}
			push_back({ t[i].l, mid, left });
			push_back({ mid, t[i].r, right });
		}

		if (nxtt.empty()) break;

		swap(nxtt, t);
	}

#ifdef LOCAL
	for (int i = 0; i <= n; i ++) {
		printf("depth: %d\n", i);
		for (int x : d[i]) printf("%d ", x);
		putchar('\n');
	}
#endif

	vector<int> layer[3];

	for (int i = 0; i <= n; i ++) layer[i % 3].push_back(i);

	for (int type : { 0, 1, 2 }) {
		for (int i = 0; i < 10; i ++) {

#ifdef LOCAL
			printf("type: %d bit: %d\n", type, i);
#endif	

			vector<int> req(n + 1);
			for (int dep : layer[type]) for (int x : d[dep]) if (x >> i & 1) req[x] = 1;
			vector<int> res = interact(req);
			for (int dep : layer[type]) for (int x : d[dep + 1]) fa[x] |= (res[x] << i);
		}
	}

	puts("!");
	for (int i = 2; i <= n; i ++) printf("%d %d\n", fa[i], i);
}

int main() {
	scanf("%d", &n);
#ifdef LOCAL
	init_local();
	puts("INITED LOCAL");
#endif
	solve();
	return 0;
}

[清华集训 2017] 无限之环

题目大意

给出一个 \(n \times m\) 的网格,每个格子有一种水管,一共有 \(15\) 种水管,你可以将非直线型的水管旋转 \(90\degree\),旋转代价为 \(1\),问将整个网格变成没有外流的水管的最小代价。

题解

主要难点在于想到费用流来解决本题。

首先考虑如何判定一个网格是没有外流的。

可以拆点,我们将一个网格点拆成 \(5\) 个点,分别是上右下左中,然后将网格点黑白染色,黑点上下左右的点都应该为白点。

将黑点的中间点连源,白点的中间点连汇,然后按照水管的连法连边,跑一遍网络流,如果最大流等于水管接口的总和除二,那么这个网格就一定没有外流的水管。

正确性显然。

然后考虑怎么表示旋转的代价。

注意题目中的一个很重要的条件,就是直线型的水管是不能被旋转的。

为什么?因为其他类型的旋转我们可以看作只有一条边移动了,而直线型旋转一次移动了两条边,建图无法保证原子性。

所以我们可以分讨一下如何建图。

一条出边的水管

直接对旋转一次,旋转两次,和逆时针旋转一次的起点和终点之间连边即可。

两条出边的水管

旋转一次可以看作后面的那个点往前面的那个点移动了,所以我们将每个点都向他后面的第二个点连边,代价都为 \(1\)

可以发现这样是正确的。

三条出边的水管

这种情况我们可以对每一种目标情况考虑如何最优旋转。

不难发现最终连法应该是将每个点向中间那个点走两步的点连边。

两边的点代价为 \(1\),中间点的代价为 \(2\)

代码

这题可能需要精细实现。

像我偷懒常数就比较大,不开 O2 无法在洛谷上通过。

#include <cstdio>
#include <queue>
#include <cassert>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;

const int NM = 2010;
const int N = NM * 10;
const int INF = 0x7f7f7f7f;
int head[N], nxt[N * 4], to[N * 4], w[N * 4], c[N * 4], tot = 1;

// string debug[N];

void add(int x, int y, int flow, int cost) {
	// cout << debug[x] << " =>  " << debug[y] << " " << " flow " << flow << " cost " << cost << " \n";

	auto add = [&](int x, int y, int flow, int cost) { nxt[++ tot] = head[x], head[x] = tot, to[tot] = y, w[tot] = flow, c[tot] = cost; };
	add(x, y, flow, cost);
	add(y, x, 0, - cost);
}

struct Node { int top, right, bottom, left, mid; };
int d[N], lst[N], flow[N], s, t;
bool v[N];
queue<int> q;

bool spfa() {
	memset(v, false, sizeof(v));
	memset(lst, -1, sizeof(lst));
	memset(d, 0x3f, sizeof(d));
	while (!q.empty()) q.pop();
	q.push(s), v[s] = true, flow[s] = INF, d[s] = 0;
	while (!q.empty()) {
		int x = q.front(); q.pop(); v[x] = false;
		for (int i = head[x]; i; i = nxt[i]) { 
			int y = to[i], cost = c[i], mf = w[i];
			if (d[y] > d[x] + cost && mf > 0) {
				d[y] = d[x] + cost, flow[y] = min(flow[x], mf), lst[y] = i;
				if (!v[y]) q.push(y), v[y] = true;
			}
		}
	}
	return lst[t] != -1;
}

pair<int, int> MCMF() {
	int maxFlow = 0, minCost = 0;
	while (spfa()) {
		maxFlow += flow[t], minCost += flow[t] * d[t];
		for (int i = t; i != s; i = to[lst[i] ^ 1]) {
			w[lst[i]] -= flow[t];
			w[lst[i] ^ 1] += flow[t];
		}
	}
	return { maxFlow, minCost };
}

int main() {
	int n, m; 
	scanf("%d%d", &n, &m);
	s = 1, t = 2;
	int cnt = 2;
	// debug[s] = "S";
	// debug[t] = "T";
	vector<vector<Node>> a(n + 1);
	for (int i = 1; i <= n; i ++) {
		a[i].resize(m + 1);
		for (int j = 1; j <= m; j ++) {
			a[i][j] = { cnt + 1, cnt + 2, cnt + 3, cnt + 4, cnt + 5 };
			// string ia = " ";
			// ia[0] = (char) (i + '0');
			// string ib = " ";
			// ib[0] = (char) (j + '0');
			// debug[a[i][j].top] = "[" + ia + "][" + ib + "] TOP";
			// debug[a[i][j].right] = "["+ia + "][" + ib + "] RIGHT";
			// debug[a[i][j].left] = "[" +ia + "][" + ib + "] LEFT";
			// debug[a[i][j].bottom] = "["+ia + "][" +ib + "] BOTTOM";
			// debug[a[i][j].mid] = "[" + ia + "][" + ib + "] MID";

			cnt += 5;
		}
	}
	// puts("init done");
	int allFlow = 0;
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= m; j ++) {
			int x; scanf("%d", &x);

			// if (!(i <= 2 && j <= 2)) continue;
			// if ((i == 3 && (j == 1 || j == 2))) continue;

			// printf("i: %d j: %d\n", i, j);
			
			auto [top, right, bottom, left, mid] = a[i][j];
			vector<int> tmp(4);
			tmp[0] = top, tmp[1] = right, tmp[2] = bottom, tmp[3] = left;

			auto smart_add = [&](int x, int y, int flow, int cost) {
				if ((i + j) % 2 == 0) swap(x, y);
				add(x, y, flow, cost);
			};

			vector<int> b;
			for (int i = 1; i <= 4; i ++) if (x >> (i - 1) & 1) {
				smart_add(mid, tmp[i - 1], 1, 0);
				b.push_back(i);
			}

			int popcnt = b.size();
			allFlow += popcnt;

			if ((i + j) % 2) {
				add(s, mid, popcnt, 0);
				if (i > 1) add(top, a[i - 1][j].bottom, 1, 0);
				if (j > 1) add(left, a[i][j - 1].right, 1, 0);
				if (j + 1 <= m) add(right, a[i][j + 1].left, 1, 0);
				if (i + 1 <= n) add(bottom, a[i + 1][j].top, 1, 0);
			} else {
				add(mid, t, popcnt, 0);
			}

			if (popcnt == 1) { // maybe no problem
				// puts("CASE 1");
				int t = b[0] - 1;
				smart_add(tmp[t], tmp[(t + 1) % 4], 1, 1);
				smart_add(tmp[t], tmp[(t - 1 + 4) % 4], 1, 1);
				smart_add(tmp[t], tmp[(t + 2) % 4], 1, 2);
			} else if (popcnt == 2) { // no problem
				// puts("CASE 2");
				int l = b[0] - 1, r = b[1] - 1;
				// printf("x: %d y: %d\n", l, r);
				if (r - l != 2) {
					smart_add(tmp[l], tmp[(l + 2) % 4], 1, 1);
					smart_add(tmp[r], tmp[(r + 2) % 4], 1, 1);
				}
			} else if (popcnt == 3) {
				// puts("CASE 3");
				int o = b[0] - 1, p = b[1] - 1, q = b[2] - 1;
				vector<int> v(4);
				v[o] = true, v[p] = true, v[q] = true;
				int mt = -1, ma = -1;
				if (v[(o - 1 + 4) % 4] && v[(o + 1) % 4]) {
					mt = (o + 2) % 4, ma = o;
				} else if (v[(p - 1 + 4) % 4] && v[(p + 1) % 4]) {
					mt = (p + 2) % 4, ma = p;
				} else {
					mt = (q + 2) % 4, ma = q;
				}
				// printf("mid: %d\n", ma);
				assert(mt != -1 && ma != -1);
				for (int i = 0; i < 4; i ++) if (v[i]) {
					if (i == ma) smart_add(tmp[i], tmp[mt], 1, 2);
					else smart_add(tmp[i], tmp[mt], 1, 1);
				}
			}
		}
	}
	// printf("allFlow: %d\n", allFlow);
	auto [maxFlow, minCost] = MCMF();
	// printf("maxFlow: %d minCost: %d\n", maxFlow, minCost);
	if (maxFlow * 2 != allFlow) puts("-1");
	else printf("%d", minCost);
	return 0;
}

[IOI2022] 最罕见的昆虫

[AGC031E] Snuke the Phantom Thief

[CF1707D] Partial Virtual Trees

[AGC043E] Topology

[UNR #6] 神隐

题目大意

交互题,你的任务是猜出一棵树。

你可以调用一个函数,作用为你给定一个边集,交互库会给你这棵树只保留这个边集后的所有连通块组成。

题解

像这种一开始并不知道任何关于询问的信息的题目,一半考虑二进制分组来制造信息。

有一个很 navie 的想法,我们直接对边的编号进行二进制分组,分别对每位为 \(0\) 和为 \(1\) 的边集进行询问。

那么如果两个点是相邻的,那么他们一定是在每一位的询问中一定会在同一个分组中的同一个连通块中出现。

但是这样需要 \(2 \log n\) 次询问。

主要问题在于我们对于询问的并行程度不够,因为我们只关心两个点是否在一个集合中,并不关心其余联通快中有什么点。

所以我们可以考虑换一种分组方式,考虑用 \(n\)\(2m\) 位恰好有 \(m\)\(1\) 的二进制数来表示每条边的编号。

然后我们对每位为 \(1\) 的边为一组,进行询问,之前的结论不变。

这样我们就只需要 \(2m\) 次询问,即 \(m \leq \frac{limit}{2}\).

又因为 \(\binom{limit}{\frac{limit}{2}} \geq n\),所以这种编号查询方案是可行的。

接下来考虑如何优化复杂度。

注意到如果一个点在 \(2m\) 次询问中有 \(m\) 次询问是在一个单独的联通块中,那么它一定是一个叶子结点,并且如果我们将所有叶子结点删去之后,这个结论同样成立。

所以我们就可以通过不断找出叶子结点并删去的方法来求出这颗树的拓扑序。

有了拓扑序就好办了。

不难发现每个联通块中最后删去的那个点一定是那个联通块中所有点的祖先。

那我们只要按照拓扑序从下往上考虑,每次遍历当前点担任最后删去点的所有联通块,那么还没被标记的点的父亲就一定是当前点。

正确性显然。

复杂度 \(\mathcal{O}(n \log n)\)

代码

#include <cstdio>
#include "tree.h"
#include <algorithm>
#include <map>
#include <queue>
using namespace std;

const int N = 2e5 + 10;
vector<int> a;
vector<vector<int>> t;
vector<vector<vector<int>>> q;
int sze[20][N], cnt[N], fa[20][N], dep[N];
vector<int> leaf;
vector<pair<int, int>> ans;
bool v[N], in[N];

vector<pair<int, int> > solve(int n) {
	int l = n <= 2000 ? 7 : 10;
	t.resize(l * 2);
	vector<int> ask(n - 1);
	for (int i = 1, cnt = 0; cnt < n; i ++) if (__builtin_popcount(i) == l) a.push_back(i), ++ cnt;
	for (int i = 0; i < l * 2; i ++) {
		for (int j = 0; j < n - 1; j ++) ask[j] = (a[j] >> i & 1);
		vector<vector<int> > res = query(ask);
		q.push_back(res);
		t[i].resize(n);
		for (int j = 0; j < res.size(); j ++) {
			sze[i][j] = res[j].size();
			for (int x : res[j])
				t[i][x] = j, cnt[x] += (sze[i][j] == 1);
		}
	}
	queue<int> leave;
	for (int i = 0; i < n; i ++) if (cnt[i] == l) leave.push(i);
	while (!leave.empty()) {
		int x = leave.front(); leave.pop();
		in[x] = true;
		for (int i = 0; i < l * 2; i ++) if (-- sze[i][t[i][x]] == 1) {
			int y = 0;
			for (int v : q[i][t[i][x]]) if (!in[v]) { y = v; break; }
			if (++ cnt[y] == l) leave.push(y);
			for (int z : q[i][t[i][x]]) if (!v[z] && z != y) ans.emplace_back(z, y), v[z] = true;
		}
	}
	// for (auto [x, y] : ans) printf("(%d, %d)\n", x, y);
	return ans;
}

[AHOI2022] 钥匙

题目大意

给你一棵树,树上有两种节点。

一种节点是宝箱节点,一种节点是钥匙节点。

\(k\) 种宝箱,钥匙只能开特定种类的包厢。

保证每种包厢对应钥匙的数量 \(\leq 5\)

\(q\) 次询问,每次询问一条路径上可以获得的礼物数量。

\(q \leq 10^6, n \leq 5 \times 10^5\)

题解

发现每种宝箱的钥匙数量很少,这个条件一定很重要。

所以我们可以试着对每个钥匙单独考虑。

可以对每个钥匙匹配所有可能与他计算答案的礼物。

如果我们直接将每个钥匙与最近的礼物考虑,这肯定是不行的。

因为我们是把每个钥匙拉出来单独考虑,直接与最近钥匙匹配后效性太大。

于是我们选择后进先出的一个匹配办法。

由于钥匙与礼物的匹配是具有方向性的,所以应该是不充不漏的。

所以现在的问题就变成了计算路径覆盖线段的数量。

那么我们对每个线段单独考虑贡献,发现其起点和终点的 dfn 应该是连续的一段区间,于是可以转换成二维数电扫描线解决。

至于如何求出钥匙和礼物的匹配关系,只需对每种颜色建出虚树然后 dfs 即可,由于每个节点只对应一种颜色,所以复杂度正确。

复杂度 \(\mathcal{O}(c \times n \log n)\).

代码

码量题……

分讨技术还是不是很熟练。

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 5e5 + 10;
int n, q, head[N], nxt[N * 2], to[N * 2], tot, sze[N], son[N], fa[N], top[N], dfn[N], cnt, dep[N], mdfn[N];
pair<int, int> a[N];
vector<int> b[N][2];
void add_edge(int x, int y) { nxt[++ tot] = head[x], head[x] = tot, to[tot] = y; }
void add(int x, int y) { add_edge(x, y), add_edge(y, x); }

int idfn[N];

void dfs1(int x, int f) {
	fa[x] = f, sze[x] = 1;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (y != f) {
			dep[y] = dep[x] + 1;
			dfs1(y, x);
			sze[x] += sze[y];
			if (!son[x] || sze[son[x]] < sze[y]) son[x] = y;
		}
	}
}

void dfs2(int x, int tp) {
	top[x] = tp, mdfn[x] = dfn[x] = ++ cnt;
	idfn[dfn[x]] = x;
	if (son[x]) dfs2(son[x], tp), mdfn[x] = max(mdfn[x], mdfn[son[x]]);
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (y != fa[x] && y != son[x]) dfs2(y, y), mdfn[x] = max(mdfn[x], mdfn[y]);
	}
}

int get_lca(int x, int y) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) return y;
	return x;
}

int kth(int x, int k) {
	while (dep[top[x]] > k) x = fa[top[x]];
	return idfn[dfn[x] - (dep[x] - k)]; 
}

int st[N], tp;
vector<int> p;

void build(int c) {
	tot = 0;
	p.clear();
	for (int x : b[c][0]) p.push_back(x);
	for (int x : b[c][1]) p.push_back(x);
	sort(p.begin(), p.end(), [&](int x, int y) { return dfn[x] < dfn[y]; });
	tp = 1, st[tp] = 1, head[1] = 0;
	// puts("AAA");
	for (int x : p) if (x != 1) {
		int lca = get_lca(st[tp], x);
		if (lca != st[tp]) {
			while (dfn[lca] < dfn[st[tp - 1]]) add(st[tp - 1], st[tp]), tp --;
			if (lca != st[tp - 1]) head[lca] = 0, add(lca, st[tp]), st[tp] = lca;
			else add(lca, st[tp --]);
		}
		head[x] = 0, st[++ tp] = x;
	}
	for (int i = 1; i < tp; i ++) add(st[i], st[i + 1]);
}

vector<int> near;

void dfs(int x, int fa, int c, int cnt) {
	if (a[x].second == c) {
		if (a[x].first == 1) ++ cnt;
		else -- cnt;
		if (cnt == 0) return near.push_back(x); 
	}
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (y != fa) {
			dfs(y, x, c, cnt);
		}
	}
}

const int Q = 1e6 + 10;
vector<pair<int, int>> reg[N][2];
vector<pair<int, int>> req[Q];
int ans[Q];
struct FenwickTree {
	int c[N];
	FenwickTree() { memset(c, 0, sizeof(c)); }
	int lowbit(int x) { return x & (-x); }
	void add(int x, int y) { for (; x <= n; x += lowbit(x)) c[x] += y; }
	int ask(int x) { int ret = 0; for (; x; x -= lowbit(x)) ret += c[x]; return ret; }
} bt;

int main() {
	// freopen("keys4.in", "r", stdin);
	// freopen("keys4.out", "w", stdout);
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; i ++) scanf("%d%d", &a[i].first, &a[i].second), b[a[i].second][a[i].first - 1].push_back(i);
	for (int i = 1, u, v; i < n; i ++) scanf("%d%d", &u, &v), add(u, v);
	dfs1(1, 1); dfs2(1, 1);
	// printf("test lca: %d\n", get_lca(2, 5));
	for (int c = 1; c <= n; c ++) if (!b[c][0].empty() && !b[c][1].empty()) {
		// printf("color: %d\n", c);
		build(c);
		// puts("build done");
		for (int x : b[c][0]) { 
			near.clear();
			dfs(x, x, c, 0);
			for (int y : near) {
				// printf("%d => %d\n", x, y);
				int lca = get_lca(x, y);
				auto insert = [&](pair<int, int> l, pair<int, int> r) {
					
					if (l.first > l.second || r.first > r.second) return;
					// printf("(%d, %d) (%d, %d)\n", l.first, l.second, r.first, r.second);
					reg[l.first][0].push_back(r);
					reg[l.second + 1][1].push_back(r);
				};
				if (lca == x) {
					int down = kth(y, dep[x] + 1);
					insert({1, dfn[down] - 1}, {dfn[y], mdfn[y]});
					insert({mdfn[down] + 1, n}, {dfn[y], mdfn[y]});
				} else if (lca == y) {
					int down = kth(x, dep[y] + 1);
					insert({dfn[x], mdfn[x]}, {1, dfn[down] - 1});
					insert({dfn[x], mdfn[x]}, {mdfn[down] + 1, n});
				} else {
					insert({ dfn[x], mdfn[x] }, { dfn[y], mdfn[y] });
				}
			}
		}
	}
	for (int i = 1, x, y; i <= q; i ++) scanf("%d%d", &x, &y), req[dfn[x]].emplace_back(dfn[y], i);
	for (int x = 1; x <= n; x ++) {
		auto add = [&](int l, int r, int k) {
			bt.add(l, k);
			bt.add(r + 1, -k);
		};
		for (auto [l, r] : reg[x][0]) add(l, r, 1);
		for (auto [l, r] : reg[x][1]) add(l, r, -1);
		for (auto [y, id] : req[x]) ans[id] = bt.ask(y);
	}
	for (int i = 1; i <= q; i ++) printf("%d\n", ans[i]);
	return 0;
}

[AHOI2022] 排列

题目大意

不好描述,考场上也是感性理解。

总之就是给你很多个环,环的总大小为 \(n\),定义一次操作为将所有元素的移向他在环上的下一位元素的位置,然后定义 \(g\) 为最少能将环变为原来的样子的操作次数。

然后题目要求的是交换任意两个不在同一个环中的元素后 \(g\) 的和。

\(n \leq 10^5\)

题解

首先一个比较显然的东西就是 \(g\) 会等于所有环长度的 lcm。

经典问题,就不证了。

其次交换两个环的元素其实就是将其合并在一起(在题目大意中的交换可能有歧义,具体见题目)。

所以问题就变成了我们有很多个数,要求合并任意两个数之后序列 lcm 的和。

注意到我们还有一个很重要的条件没有用,就是环长度的总和为 \(n\)

一个经典的 trick,长度相同的环的个数至多只有 \(\sqrt{n}\) 个,且我们合并两个环求和并不关心环中有什么元素。

所以不难想到我们可以直接枚举我们要合并哪两个环。

接下来我们要解决的问题就是如何动态维护一个序列的 lcm。

注意到我们其实每次只会做删去两个元素,加入一个元素的操作。

所以我们只要维护每个质因子的前三大的次数就好了。

同时可以通过线性筛预处理出所有数的质因子。

总的复杂度 \(\mathcal{O}(n \log n)\)

代码

实现有些细节,需要注意下。

一开始只对了第一个 task 的时候竟然没意识到是多测没清空。

#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
using namespace std;

typedef long long ll;
const int N = 1e6 + 10;
const ll MOD = 1e9 + 7;
int n, a[N], c[N], sze[N], cnt[N], tot, b[N];
bool v[N];

ll qpow(ll a, ll b) {
	ll ret = 1;
	for (; b; b >>= 1) {
		if (b & 1) ret = ret * a % MOD;
		a = a * a % MOD;
	}
	return ret;
}

int gcd(int x, int y) {
	if (y == 0) return x;
	return gcd(y, x % y);
}
int find(int x) { return c[x] == x ? x : c[x] = find(c[x]); }
void merge(int x, int y) {
	int fx = find(x), fy = find(y);
	if (fx != fy) {
		c[fx] = fy;
		sze[fy] += sze[fx];
	}
}

ll res, ans;
int bucket[N], maxp[N][3];

int md[N], p[N], pcnt, inv[N];
bool pv[N];
vector<int> pf[N];

void insert(int x) {
	// printf("insert %d\n", x);
	for (int factor : pf[x]) bucket[factor] = 0;
	for (int factor : pf[x]) bucket[factor] ++;
	for (int factor : pf[x]) if (bucket[factor]) {
		// printf("has factor: %d ^ %d\n", factor, bucket[factor]);
		if (bucket[factor] > maxp[factor][0]) {
			res = res 
				* inv[qpow(factor, maxp[factor][0])] % MOD 
				* qpow(factor, bucket[factor]) % MOD;
			maxp[factor][2] = maxp[factor][1];
			maxp[factor][1] = maxp[factor][0];
			maxp[factor][0] = bucket[factor];
		} else if (bucket[factor] > maxp[factor][1]) {
			maxp[factor][2] = maxp[factor][1];
			maxp[factor][1] = bucket[factor];
		} else if (bucket[factor] > maxp[factor][2]) {
			maxp[factor][2] = bucket[factor];
		}
		bucket[factor] = 0;
	}
}

void remove(int x) {
	for (int factor : pf[x]) bucket[factor] = 0;
	for (int factor : pf[x]) bucket[factor] ++;
	for (int factor : pf[x]) if (bucket[factor]) {
		if (maxp[factor][0] == bucket[factor]) {
			maxp[factor][0] = maxp[factor][1];
			maxp[factor][1] = maxp[factor][2];
			maxp[factor][2] = 0;
			res = res 
				* inv[qpow(factor, bucket[factor])] % MOD 
				* qpow(factor, maxp[factor][0]) % MOD;
		} else if (maxp[factor][1] == bucket[factor]) {
			maxp[factor][1] = maxp[factor][2];
			maxp[factor][2] = 0;
		} else if (maxp[factor][2] == bucket[factor]) {
			maxp[factor][2] = 0;
		}
		bucket[factor] = 0;
	}
}

void init() {
	memset(cnt, 0, sizeof(cnt));
	memset(v, false, sizeof(v));
	res = 1;
	ans = 0;
	tot = 0;
	memset(maxp, 0, sizeof(maxp));
}

void solve() {
	init();
	cin >> n;
	for (int i = 1; i <= n; i ++) c[i] = i, sze[i] = 1;
	for (int i = 1; i <= n; i ++) cin >> a[i], merge(i, a[i]);
	for (int i = 1; i <= n; i ++) 
		if (find(i) == i) 
			insert(sze[i]), ++ cnt[sze[i]], 
				!v[sze[i]] && (b[++ tot] = sze[i], v[sze[i]] = true);
	for (int i = 1; i <= tot; i ++) {
		ll ret;
		auto tmp_insert = [&](int x) {
			ret = res;
			for (int factor : pf[x]) bucket[factor] = 0;
			for (int factor : pf[x]) bucket[factor] ++;
			for (int factor : pf[x]) if (bucket[factor]) {
				if (bucket[factor] > maxp[factor][0]) {
					ret = ret * inv[qpow(factor, maxp[factor][0])] % MOD 
						* qpow(factor, bucket[factor]) % MOD;
				}
				bucket[factor] = 0;
			}
		};
		int t = b[i];
		if (cnt[t] > 1) {
			remove(t), remove(t);
			tmp_insert(t + t);
			ans = (ans 
				+ (1ll * cnt[t] * (cnt[t] - 1) % MOD) 
				* t % MOD * t % MOD * ret % MOD) % MOD;
			insert(t), insert(t);
		}
		for (int j = i + 1; j <= tot; j ++) {
			int g = b[j];
			remove(t), remove(g);
			tmp_insert(t + g);
			ans = (ans 
				+ 2 * 
					t % MOD * cnt[t] % MOD 
					* g % MOD * cnt[g] % MOD 
					* ret % MOD
			) % MOD;
			insert(t), insert(g);
		}
	}
	cout << ans << "\n";
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	// freopen("perm3.in", "r", stdin);
	for (int i = 2; i <= 1e6; i ++) {
		if (!v[i]) p[++ pcnt] = i, md[i] = i;
		for (int j = 1; j <= pcnt && i * p[j] <= 1e6; j ++) {
			v[i * p[j]] = true, md[i * p[j]] = p[j];
			if (i % p[j] == 0) break;
		}
	}
	inv[0] = 1, pf[1].push_back(1);
	for (int i = 1; i <= 1e6; i ++) inv[i] = qpow(i, MOD - 2);
	for (int i = 2; i <= 1e6; i ++) {
		int x = i;
		while (x > 1) {
			pf[i].push_back(md[x]);
			x /= md[x];
		}
	}
	int T; cin >> T;
	while (T --) solve();
	return 0;
}

[SDOI2013] 淘金

题目大意

定义 \(f(x)\)\(x\) 每一位数的乘积。

在平面内对 \((n, n)\) 内对点都做一个变换,将 \((x, y)\) 移到 \((f(x), f(y))\)

求变换后拥有点个数的点的前 \(k\) 大。

\(n \leq 10^{12}\)

题解

一个很重要的性质就是本质不同的 \(f(x)\) 值其实很少。

打表可以发现大概是 \(10^4\) 次方级别的。

所以我们就可以用数位 dp 求出每种不同的 \(f(x)\) 所对应的 \(x\) 有多少。

现在问题就变成了给定两个单调的序列 \(\{a_n\}\),如何求出 \(a_x+a_y\) 的前 \(k\) 大?

考虑贪心,对每一个 \(x\) 来说,最小的肯定是 \(a_x+a_1\)

所以只有当 \(a_x+a_1\) 已经被计算过之后才可能会去计算 \(a_x+a_2\)

那么我们就可以使用一个优先队列来存储所有可能的答案,那么每次我们取出一个数 \(a_x+a_y\) 后,我们将 \(a_x+a_{y+1}\) 放入队列中,直到取完前 \(k\) 大。

代码

有些细节,比如说数位 dp 数组计算时不能取模,排序时直接相乘可能会爆 long long,所以要改成相除。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 14;
const int P = 8300;
const ll MOD = 1e9 + 7;
struct Node { int i, j; };
int n, k, a[N], b[N], cnt;
ll f[N][P][2][2], q[P], g[P], ans, m;
map<ll, int> p;
priority_queue<Node> pq;

bool operator < (const Node &x, const Node &y) { return 1.0 * g[x.i] / g[y.j] < 1.0 * g[y.i] / g[x.j]; };

void dfs(int x, int lst, ll mul) {
	if (x > 12) {
		if (p.find(mul) == p.end()) p.emplace(mul, ++ cnt);
		return;
	}
	for (int i = lst; i <= 9; i ++) dfs(x + 1, i, mul * i);
}

void init() {
	dfs(1, 1, 1);
	for (auto cp : p) q[cp.second] = cp.first;
}

int main() {
	init();
	scanf("%lld%d", &m, &k);
	ll tm = m;
	while (tm) b[++ n] = tm % 10, tm /= 10;
	for (int i = 1; i <= n ; i ++) a[i] = b[n - i + 1];
	f[0][p[1]][0][1] = 1;
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= cnt; j ++)
			for (int flag : { 0, 1 } )
				for (int pre : { 0, 1 })
					for (int nxt = 0; nxt <= (flag ? 9 : a[i]); nxt ++)
						f[i][(pre & (nxt == 0)) ? j : p[q[j] * nxt]][flag | (nxt < a[i])][pre & (nxt == 0)] += f[i - 1][j][flag][pre];
	for (int i = 1; i <= cnt; i ++) g[i] = (f[n][i][0][0] + f[n][i][1][0]);
	for (int i = 1; i <= n; i ++) if (q[i] > m) g[i] = 0;
	sort(g + 1, g + 1 + cnt, [&](ll x, ll y) { return x > y; });
	for (int i = 1; i <= cnt; i ++) pq.push({ 1, i });
	while (!pq.empty() && k --) {
		Node x = pq.top(); pq.pop();
		ans = (ans + g[x.i] % MOD * (g[x.j] % MOD) % MOD) % MOD;
		if (x.i + 1 <= cnt) pq.push({ x.i + 1, x.j });
	}
	printf("%lld\n", ans);
	return 0;
}

[WC2021] 表达式求值

题目大意

给你一个只包含求 max 和求 min 的表达式,表达式中只有 \(n\) 个变量,没有常量。

表达式中可能会含有问号,表示这个操作可能是取 max 也可能是取 min

\(q\) 次询问,每次询问会给出表达式中所有量的值,要求求出所有可能的表达式的结果的和。

\(q \leq 5 \times 10^4, n \leq 10\)

题解

首先比较简单的是我们可以对表达式建出表达式树。

然后由于可能的值的个数比较少,所以我们可以设计一个树形 dp。

\(f_{i,j}\) 表示在第 i 个节点,值为 j 的可能的个数。

这样肯定是不能过的。

所以有一个比较经典的trick,由于我们只有 max 和 min 操作,所以我们只关心数之间的大小关系,并不关心数具体是多少。

所以我们可以预处理出所有可能的 \(2^{10}\) 个大小关系。

然后因为相等关系不好考虑,所以我们可以进行容斥。

最终我们就可以 \(\mathcal{O}(2^n \times \left| S \right| \times n)\) 预处理,\(\mathcal{O}(n)\) 回答每次询问了。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 5e4 + 10;
const int M = 10;
const ll MOD = 1e9 + 7;
struct Node { int lc, rc; char val; } t[N * 4];
int a[M + 1][N], n, m, tot, mask, rt, b[M + 1];
char s[N];
ll f[N][2], g[1 << M], ans;

void build(int &x, int l, int r) {
	if (l == r) return t[x = ++ tot] = { 0, 0, s[l] }, void();
	if (s[l] == '(' && s[r] == ')') return build(x, l + 1, r - 1);
	for (int i = r, cnt = 0; i >= l; i --) {
		cnt += s[i] == '(' ? 1 : (s[i] == ')' ? -1 : 0);
		if (cnt == 0 && (s[i] == '<' || s[i] == '>' || s[i] == '?')) return t[x = ++ tot] = { 0, 0, s[i] }, build(t[x].lc, l, i - 1), build(t[x].rc, i + 1, r), void();
	}
}

void dp(int x) {
	int lc = t[x].lc, rc = t[x].rc; char val = t[x].val;
	f[x][0] = f[x][1] = 0;
	if (!lc && !rc) return f[x][(mask >> (val - '0') & 1) ^ 1] = 1, void();
	dp(lc), dp(rc);
	ll t = 0; 
	for (int v : { 0, 1 }) for (int lv: { 0, 1 }) for (int rv : { 0, 1 }) {
		t = f[lc][lv] * f[rc][rv] % MOD;
		if (min(lv, rv) == v && (val == '?' || val == '<')) f[x][v] = (f[x][v] + t) % MOD;
		if (max(lv, rv) == v && (val == '?' || val == '>')) f[x][v] = (f[x][v] + t) % MOD; 
	}
}

void solve(int col) {
	for (int i = 1; i <= m; i ++) b[i] = i - 1;
	sort(b + 1, b + 1 + m, [&](int x, int y) { return a[x + 1][col] < a[y + 1][col]; });
	for (int i = 1, S = 0, T = 0; i <= m; i ++) T = S, S |= 1 << b[i], ans = (ans + a[b[i] + 1][col] * (g[T] - g[S] + MOD) % MOD) % MOD;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i ++) for (int j = 1; j <= n; j ++) scanf("%d", &a[i][j]);
	scanf("%s", s + 1); 
	build(rt, 1, strlen(s + 1));
	for (mask = 0; mask < 1 << m; mask ++) dp(rt), g[mask] = f[rt][1];
	for (int i = 1; i <= n; i ++) solve(i);
	return printf("%lld", ans), 0;
}

[HEOI2016/TJOI2016]排序

题目大意

给定一个序列,有 \(q\) 次操作,每次操作为将序列的一个子区间升序或降序排序,问 \(q\) 次操作后序列第 \(k\) 位的值。

题解

和上一题一样,我们只关心一个位置的值,那么实际上在排序中我们其实只关心我们排序的那个数和我们最终求的数的大小关系,于是就可以二分,然后使用一个数据结构来维护 0/1 的个数,就做完了。

复杂度 \(\mathcal{O}(n {\log}^2 n)\)

代码

尝试了波新的线段树写法,感觉还行。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
struct SegTree { int cnt[2], tag; } t[N * 4];
struct Node { int op, l, r; } req[N];
int a[N], b[N], n, q, k;

#define cnt(x, k) t[x].cnt[k]
#define tag(x) t[x].tag
#define lc(x) ((x) * 2)
#define rc(x) ((x) * 2 + 1)
void apply(int x, int cover, int size) { cnt(x, 0) = cnt(x, 1) = 0; cnt(x, cover) = size; tag(x) = cover; }
void pushup(int x) { for (int p : { 0, 1 }) cnt(x, p) = cnt(lc(x), p) + cnt(rc(x), p); }
// there is a problem in pushdown
void pushdown(int x, int l, int r) { int mid = (l + r) >> 1; if (tag(x) != -1 && l != r) apply(lc(x), tag(x), mid - l + 1), apply(rc(x), tag(x), r - mid), tag(x) = -1; }
void build(int x, int l, int r) {
	cnt(x, 0) = cnt(x, 1) = 0; tag(x) = -1;
	if (l == r) return cnt(x, b[l]) = 1, void();
	int mid = (l + r) >> 1;
	build(lc(x), l, mid), build(rc(x), mid + 1, r), pushup(x);
}
void modify(int x, int l, int r, int ql, int qr, int k) {
	if (ql > qr) return;
	if (ql <= l && r <= qr) return apply(x, k, r - l + 1);
	pushdown(x, l, r); int mid = (l + r) >> 1;
	if (ql <= mid) modify(lc(x), l, mid, ql, qr, k);
	if (mid < qr) modify(rc(x), mid + 1, r, ql, qr, k);
	pushup(x);
}
int query(int x, int l, int r, int ql, int qr, int k) {
	if (ql <= l && r <= qr) return cnt(x, k);
	pushdown(x, l, r); int mid = (l + r) >> 1, ret = 0;
	if (ql <= mid) ret += query(lc(x), l, mid, ql, qr, k);
	if (mid < qr) ret += query(rc(x), mid + 1, r, ql, qr, k);
	return ret;
}

bool check(int lim) {
	for (int i = 1; i <= n; i ++) b[i] = a[i] >= lim;
	build(1, 1, n);
	for (int i = 1, t0, t1; i <= q; i ++) 
		t0 = query(1, 1, n, req[i].l, req[i].r, 0), t1 = req[i].r - req[i].l + 1 - t0,
		(req[i].op 	? (modify(1, 1, n, req[i].l, req[i].l + t1 - 1, 1), modify(1, 1, n, req[i].r - t0 + 1, req[i].r, 0))
					: (modify(1, 1, n, req[i].l, req[i].l + t0 - 1, 0), modify(1, 1, n, req[i].r - t1 + 1, req[i].r, 1))
		);
	return query(1, 1, n, k, k, 1);
}

int main() {
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	for (int i = 1; i <= q; i ++) scanf("%d%d%d", &req[i].op, &req[i].l, &req[i].r);
	int l = 1, r = n, ans = 0; scanf("%d", &k);
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (check(mid)) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	return printf("%d", ans), 0;
}

[AHOI2022] 回忆