闲话 23.3.26

闲话

闲话【碎片】(7/?)

这时候是不是该写历年省选真题了?
是不是该多写几年的啊?

怎么这些天闲话阅读量低迷啊?
是不是没有多项式就没人看啊?

今日推歌:美影日记
大概我下午不困吧 我挺精神的

杂题

CF1776J

给定一张 \(n\) 个点 \(m\) 条边的无向连通图 \(G_0 = (V_0, E_0)\),点集 \(V_0\) 中编号为 \(i\) 的点有颜色 \(c_i(1\le c_i\le 3)\)

定义 \(G_{i - 1} = (V_{i - 1}, E_{i - 1})\) 的拓张生成了 \(G_i = (V_i, E_i)\)\(G_i\) 定义如下:

  • 对每个点 \(s\in V_{i - 1}\)\(V_i\) 中都包含两个与 \(s\) 颜色相同的点 \(s_1, s_2\)\(s_1, s_2\) 间的连边 \((s_1, s_2) \in E_i\)
  • 对每条边 \((s, t) \in E_{i - 1}\):若 \(s, t\) 颜色相同,则 \((s_1, t_1), (s_2, t_2) \in E_i\);反之 \((s_1, t_2), (s_2, t_1) \in E_i\)

定义一张无向连通图的直径为图上任意两点间最短路的最大长度。

给定 \(k\),求 \(G_k\) 的直径。

\(2\le n\le 100, \ 0\le k\le 100, \ n - 1\le m\le n(n - 1) / 2\)

这是 *2500?

首先需要刻画 \(G_k\) 的形态。

节点 \(u\in V_0\)\(G_0\to G_k\) 的过程中被倍增地复制,因此我们可以用所有长度为 \(k\) 的二进制串 \(b\) 来标明 \(G_k\)\(u\) 对应的 \(2^k\) 个节点,对一个特定的串 \(b\),记它指代的节点为 \([u, b]\)。例如 \([u, \texttt{001}]\) 指代了 \(((u_1)_2)_2\)。在有上下文的情况下不声明二进制串的长度。
\(u\in V_0\) 倍增得到的 \(2^k\)\(G_k\) 中的节点,有以下性质:假设有两个节点 \([u, b_1], [u, b_2]\in V_{k}\),则 \([u, b_1]\)\([u, b_2]\) 有连边当且仅当二进制串 \(b_1, b_2\) 之间的汉明距离 \(\Delta(b_1, b_2)\)\(1\)。这不难通过归纳法证明。

对一条边 \((u, v)\in E_0\),假设 \(u, v\) 的颜色相同,则在 \(G_k\) 中节点 \([u, b]\)\([v, b]\) 有连边;反之记二进制串 \(b\) 按位取反得到了二进制串 \(\overline b\)\([u, b]\)\([v, \overline b]\) 有连边。这也可以通过归纳法证明。

随后考虑如何计算新图的直径。

一条 \(G_k\) 上长度为 \(l\) 的路径 \([u_1, b_{u_1}], [u_2, b_{u_2}], \dots, [u_l, b_{u_l}]\) 是奇的,当且仅当存在 \(2i + 1\) 个位置 \(p > 1\),满足 \((u_{p - 1}, b_{u_{p - 1}})\)\((u_{p}, b_{u_{p}})\) 的颜色不相同;类似地定义偶路径。也就是说,我们定义路径的奇偶性是路径经过的端点颜色不同的边的数量的奇偶性。

下面的定理给出了一种计算答案的方法。
我们断言,\(G_k\) 内节点 \([u, b_u]\)\([v, b_v]\) 的最短路是以下两个值中较小的值。

  1. \(u\)\(v\) 之间的最短奇路径的长度 \(+\ \Delta(b_u, b_v)\)
  2. \(u\)\(v\) 之间的最短偶路径的长度 \(+\ \Delta(\overline {b_u}, b_v)\)

证明:
考虑 \(G_k\) 内一条长度为 \(l\) 的路径 \([u_1, b_{u_1}], [u_2, b_{u_2}], \dots, [u_l, b_{u_l}]\),其中 \([u_1, b_{u_1}] = [u, b_u]\)\([u_l, b_{u_l}] = [v, b_v]\)。由于 \(G_k\) 的对称性,我们总能将路径中形如 \([u, b_1]\to [u, b_2]\) 的一步放到这条路径的最后走。因此不失一般性地,我们假设存在一个位置 \(i\)\(\forall j < i, u_j \neq u_{j + 1}\)\(\forall j\ge i,\ u_{j} = u_{j + 1}\)。发现这个全相同的后缀(即 \(i\sim l\) 段路径)节点对应的 \(G_0\) 节点均为 \(v\)
考虑把这条路径投影到 \(G_0\) 上,得到路径 \(u_1, u_2, \dots, u_l\)。仍然考虑上面的位置 \(i\),我们发现 \(1\sim i\) 段的路径长度就是 \(u\)\(v\) 之间的最短路径的长度。可以发现,如果 \(1\sim i\) 段路径是偶的,则 \([u_i, b_{u_i}] = [v, b_u]\);反之 \([u_i, b_{u_i}] = [v, \overline{b_u}]\)。这点可以从上面的若干结论中得出。因此当 \(1\sim i\) 段路径是偶路径时,\(i\sim l\) 段路径的长度就是 \(\Delta(b_u, b_v)\),反之是 \(\Delta(\overline {b_u}, b_v)\)
这就完成了证明。

我们其实不需要关注从任意 \([u, b_u]\) 起始的路径。由 \(G_k\) 的对称性可以知道,我们总能把路径 \([u_1, b_{u_1}], [u_2, b_{u_2}], \dots, [u_l, b_{u_l}]\)\(2\sim l\) 段的二进制串 \(b_{u_i}\)\(b_{u_1}\) 做异或,得到等价的路径 \([u_1, \texttt{00...0}], [u_2, (b_{u_2})'], \dots, [u_l, (b_{u_l})']\)。因此我们在计算直径时,只需要考虑从 \([u, \texttt{00...0}]\) 起始的路径。

取两个节点 \(u, v\in V_0\),考虑确定 \(b\) 使得 \([u, \texttt{00...0}]\)\([v, b]\) 的路径长度最大。根据上述的定理,前半段路径总是从 \([u, \texttt{00...0}]\)\([v, \texttt{00..0}]\) 的最短偶路径或从 \([u, \texttt{00...0}]\)\([v, \texttt{11...1}]\) 的最短奇路径。随后设 \(b\) 中的 \(\texttt{1}\) 数量为 \(x\),不难发现 \([v, \texttt{00...0}]\to [v, b]\) 的长度是 \(x\)\([v, \texttt{11...1}]\to [v, b]\) 的长度是 \(k - x\)
这样我们设 \(\text{dis}_0(u, v)\)\(u\)\(v\) 的最短偶路径,\(\text{dis}_1(u, v)\)\(u\)\(v\) 的最短奇路径,\([u, \texttt{00...0}]\)\([v, b]\) 的最长路径长度可以写成

\[\max_{0\le x\le k}\{ \text{dis}_0(u, v) + x, \text{dis}_1(u, v) + k - x\} \]

对确定源点的 \(\text{dis}_0, \text{dis}_1\) 可以通过一次 bfs 得到,随后只需要枚举 \(u, v, x\) 计算长度即可。总时间复杂度 \(O(nm + n^2 k)\)

Submission.



P3863

给定一个长度为 \(n\) 的序列,给出 \(q\) 个操作,形如:

1 l r x 表示将序列下标介于 \([l,r]\) 的元素加上 \(x\)(请注意,\(x\) 可能为负)

2 p y 表示查询 \(a_p\) 在过去的多少秒时间内不小于 \(y\)(不包括这一秒)

开始时为第 \(0\) 秒,第 \(i\) 个操作发生在第 \(i\) 秒。

\(2 \leq n,q \leq 100000\)\(1 \leq l \leq r \leq n\)\(1 \leq p \leq n\)\(-10^9 \leq x,y,a_i \leq 10^9\)

水紫时刻!

先想 \(n = 1\) 怎么做。考虑维护时间序列 \(b\),也就是第 \(i\) 秒的值为 \(b_i\)。我们发现 1. 操作就是对 \(b\) 的一个后缀 \(+x\),2. 操作就是查询 \(b\) 的前缀中 \(\ge y\) 的位置数。
这样我们只需要分块维护有序块即可。可以套基数排序做到 \(O(n\sqrt{n\log n})\)

\(n > 1\) 时可以离线操作,做扫描线,过程中维护 \(b\) 序列。
1.操作就是在 \(l\) 位置后缀 \(+x\)\(r +1\) 位置后缀 \(-x\),2. 操作不变。

\(O(n)\) 次修改和查询,总时间复杂度 \(O(n\sqrt{n\log n})\)。懒得写基数排序,\(O(n\sqrt n\log n)\) 也能过。

code
#include <bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb push_back
#define pb pop_back
const int N = 1e5 + 10, B = 350;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, q, a[N], typ, t1, t2, t3, sqr, bel[N], lp[N], rp[N], ans[N], qcnt;
struct query {
	int l, r, w, id;
}; vector<query> qu[N];

struct block {
	int a[N], sorted_a[N], siz, lzy;
	inline void resize(int __siz) { siz = __siz; }
	inline void pdown() {
		rep(i,1,siz) a[i] += lzy;
		lzy = 0;
	}
	inline void pup() {
		memcpy(sorted_a + 1, a + 1, sizeof(int) * siz);
		sort(sorted_a + 1, sorted_a + siz + 1);
	} 
	inline void add_block(int va) {
		lzy += va;
	}
	inline void add_side(int l, int r, int va) {
		pdown(); 
		rep(i,l,r) a[i] += va;
		pup();
	}
	inline int query_block(int va) {
		va -= lzy; 
		int id = lower_bound(sorted_a + 1, sorted_a + 1 + siz, va) - sorted_a;
		return siz - id + 1;
	} 
	inline int query_side(int l, int r, int va) {
		int ret = 0;
		rep(i,l,r) if (a[i] + lzy >= va) ++ ret;
		return ret;
	}
	inline void debug() {
		rep(i,1,siz) cout << a[i] + lzy << ' '; cout << '\n';
		rep(i,1,siz) cout << sorted_a[i] + lzy << ' '; cout << '\n';
	}
} bl[B];

signed main() {
	cin >> n >> q;
	rep(i,1,n) cin >> a[i];
	rep(i,1,q) {
		cin >> typ;
		if (typ == 1) cin >> t1 >> t2 >> t3, qu[t1].eb( { i, q, t3, 0 } ), qu[t2 + 1].eb( { i, q, - t3, 0 } );
		else ++ qcnt, cin >> t1 >> t2, qu[t1].eb( { 1, i - 1, t2, qcnt } ), ans[qcnt] += (a[t1] >= t2);
	} sqr = sqrt(q); 
	rep(i,1,q) {
		bel[i] = (i - 1) / sqr + 1;
		if (bel[i] != bel[i - 1]) lp[bel[i]] = i, rp[bel[i - 1]] = i - 1;
	} rp[bel[q]] = q;
	rep(i,1,bel[q]) bl[i].resize(rp[i] - lp[i] + 1);
	rep(i,1,n) {
		rep(j,1,bel[q]) bl[j].add_block(a[i]);
		sort(qu[i].begin(), qu[i].end(), [&](auto a, auto b){return a.id < b.id;});
		for (auto v : qu[i]) {
			if (v.l > v.r) continue;
			int bll = bel[v.l], blr = bel[v.r];
			if (v.id == 0) {
				if (bll == blr) {
					bl[bll].add_side(v.l - rp[bll - 1], v.r - rp[bll - 1], v.w);
				} else {
					bl[bll].add_side(v.l - rp[bll - 1], rp[bll] - rp[bll - 1], v.w);
					rep(j,bll + 1,blr) bl[j].add_block(v.w);
				}
			} else {
				if (bll == blr) {
					ans[v.id] += bl[bll].query_side(v.l - rp[bll - 1], v.r - rp[bll - 1], v.w);
				} else {
					rep(j,bll,blr - 1) ans[v.id] += bl[j].query_block(v.w);
					ans[v.id] += bl[blr].query_side(1, v.r - rp[blr - 1], v.w);
				}
			}
		}
		rep(j,1,bel[q]) bl[j].add_block(- a[i]);
	} rep(i,1,qcnt) cout << ans[i] << '\n';
}



P7514

Alice 有 \(n\) 张卡牌,第 \(i\)\(1 \le i \le n\))张卡牌的正面有数字 \(a_i\),背面有数字 \(b_i\),初始时所有卡牌正面朝上。

现在 Alice 可以将不超过 \(m\) 张卡牌翻面,即由正面朝上改为背面朝上。Alice 的目标是让最终朝上的 \(n\) 个数字的极差(最大值与最小值的差)尽量小。请你帮 Alice 算一算极差的最小值是多少。

\(3 \le n \le {10}^6\)\(1 \le m < n\)\(1 \le a_i, b_i \le {10}^9\)

考虑到要求的是最大值与最小值的差,我们不妨记录所有数字以及对应的面,按照数字从小到大排序。记第 \(i\) 个元素为 \(\{c_i, 0/1\}\)

考虑若一段区间 \([l, r]\) 可以构造出合法的翻面方案,则 \(c_r - c_l\) 就可以作为答案。可以构造出合法的翻面方案,当且仅当不存在正反面都被排除在外的牌,并且仅包含反面的牌的数量不超过 \(m\)
容易发现当固定左端点时,右端点一直向外延伸,要么到达序列的尽头,要么找到合法方案。因此我们可以采用双指针的方式求解。

总时间复杂度 \(O(n\log n)\),瓶颈在排序。

code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 1e6 + 10;
int n, m, a[N], b[N];

struct card {
    int val, id, fc;
    bool operator< (const card& b) const {
        return val < b.val;
    }
} c[N << 1];

int cnt[3], cnt1;
int occ[N];
void add(int id) {
    cnt[occ[c[id].id]] --;
    occ[c[id].id] ++;
    cnt[occ[c[id].id]] ++;
    if (c[id].fc == 0) ++ cnt1;
}
void del(int id) {
    cnt[occ[c[id].id]] --;
    occ[c[id].id] --;
    cnt[occ[c[id].id]] ++;
    if (c[id].fc == 0) -- cnt1;
}
bool check() {
    return cnt[0] == 0 and n - cnt1 <= m;
}

signed main() {
    cin >> n >> m;
    rep(i,1,n) cin >> a[i], c[i] = { a[i], i, 0 };
    rep(i,1,n) cin >> b[i], c[i + n] = { b[i], i, 1 };
    cnt[0] = n;
    sort(c + 1, c + 1 + n * 2);
    long long ans = 1e18;
    rep(i,1,n) add(i);
    for (int l = 1, r = n; l <= 2 * n; del(l), ++ l) {
        while (r < 2 * n and !check()) {
            ++ r, add(r);
        }
        if (check()) ans = min(ans, 1ll * c[r].val - c[l].val);
    } cout << ans << '\n';
} 



P7516

对于一张 \(n\) 个点 \(m\) 条边的有向图 \(G\)(顶点从 \(1 \sim n\) 编号),定义函数 \(f(u, G)\)

  1. 初始化返回值 \(cnt = 0\),图 \(G' = G\)
  2. \(1\)\(n\) 按顺序枚举顶点 \(v\),如果当前的图 \(G'\) 中,从 \(u\)\(v\) 与从 \(v\)\(u\) 的路径都存在,则将 \(cnt + 1\),并在图 \(G'\) 中删去顶点 \(v\) 以及与它相关的边。
  3. \(2\) 步结束后,返回值 \(cnt\) 即为函数值。

现在给定一张有向图 \(G\),请你求出 \(h(G) = f(1, G) + f(2, G) + \cdots + f(n, G)\) 的值。

更进一步地,记删除(按输入顺序给出的)第 \(1\)\(i\) 条边后的图为 \(G_i\)\(1 \le i \le m\)),请你求出所有 \(h(G_i)\) 的值。

\(2 \le n \le {10}^3\)\(1 \le m \le 2 \times {10}^5\)\(1 \le x_i, y_i \le n\)

考虑固定图的形态,求 \(h(G)\)

首先可以发现,对一个点 \(u\)\(f(u, G)\) 只能由 \(v \le u\) 贡献,这是因为 \(u\) 肯定能对 \(f(u, G)\) 贡献,他把自己删掉了就不可能再出现合法点了。因此我们固定一对点 \((u, v)\text{ s.t. }u > v\),看什么情况会产生贡献。
首先假定已经能产生贡献了,也就是 \(u\to v, v\to u\) 有路径。我们选一个点 \(w < v < u\),如果 \(u\to v\) 的路径经过了 \(w\),则可以发现,\(u\to w\to v\) 有路径,\(v\to u\) 也有路径。这能推知 \(u\to w, w\to v\to u\) 有路径。也就是说,\(w\) 一定在 \(v\) 之前就被删除了。归纳可以发现,点对 \((u, v)\) 有贡献,当且仅当 \(u\to v, v\to u\) 有不经过 \([1, v)\) 内点的路径。

这样我们就有一个做法了。
我们不需要具体地计算每个 \(f\),而只要对当前的图计算 \(h\),所以我们可以枚举 \(u, v\)\(> v\) 的中介点 \(w\),只要存在一条路径就有解。这和 Floyed 传递闭包是同构的,我们可以在 \(O(n^3)\) 的复杂度内得到固定的 \(G\) 的解。
对于动态删边,转换成动态加边。我们对每条边赋一个边权,权值是它的输入编号。我们考虑用上面的 Floyed 算法跑 \(\text{min}\) 最短路,假设 \((u, v)\) 的答案是 \(k\),则可以知道它对任意 \(<k\) 的询问都有贡献。差分即可。
总时间复杂度 \(O(n^3 + m)\),常数小所以可过。

从这个观察中我们可以做一些优化。
仍然考虑转换成动态加边,但是我们每次加一条边,维护当前答案。我们维护 \([0, i, j]\) 表示原图上 \(i\to j\) 只经过 \(\ge i\) 的点是否可行的命题,\([1, i, j]\) 表示反图上 \(i\to j\) 只经过 \(\ge i\) 的点是否可行的命题。答案即为 \(\sum_{i, j} [[0, i, j] \land [1, i, j]]\)\([0, i, j]\)\([1, i, j]\) 是对称的,我们只需要讨论其中一个的维护方法即可。
假设当前加入了边 \(s\to t\),则我们找到所有 \([0, i, s] = 1\)\([0, i, t] = 0\)\(i\),置 \([0, i, t] = 1\) 并从 \(t\) 开始 dfs,把搜到的所有 \([0, i, j] = 0\)\(j\) 置为 \(1\)。考虑复杂度。前面的部分对 \(m\) 条边要扫 \(O(n)\)\(i\),复杂度是 \(O(nm)\) 的;后半部分均摊一下,每个 \((i, j)\) 只会被置为 \(1\) 一次,每次对复杂度的贡献是 \(O(n)\) 的,所以这部分的总时间复杂度是 \(O(nm + n^3)\) 的。
……优化?复杂度好像没变啊?

考虑我们只需要维护 01,所以 bitset 优化。记一个 bitset \(b\) 的第 \(i\) 位为 \(b[i]\)
我们把 \([t, i, j]\) 放到两个 bitset \(A_{t, i}[j]\)\(B_{t, j}[i]\) 中,图的邻接矩阵 \([0/1 : i\to j\ 有边]\) 放到另一个 bitset \(C_{t, i}[j]\) 中。
接着上面的讨论,前面的部分要找 dfs 起点,我们要找的位置就是 \(B_{0, s} \text{ and } (!B_{0, t})\)\(\le t\)\(1\) 位置,后半部分扫 \(i\to j\) 的出边时需要的就是 \(!(A_{0, i})\text{ and } C_{0, i}\)\(\ge i\)\(1\) 位置。
总时间复杂度 \(O(n^3 / \omega)\)

O(n^3) code
#include <bits/stdc++.h>
using namespace std;
#define inline __attribute__((__gnu_inline__, __always_inline__, __artificial__)) inline
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb push_back
#define pb pop_back
const int N = 1000 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, m, dis[N][N], t1, t2;
long long ans[N * 300];

signed main() {
	cin >> n >> m;
	rep(i,1,m) cin >> t1 >> t2, dis[t1][t2] = i;
	pre(k,n,1) {
		rep(j,k + 1,n) ans[min(dis[k][j], dis[j][k])] ++;
		rep(i,1,n) if (dis[i][k]) {
			int tmp = dis[i][k];
			if (i < k) rep(j,1,n) dis[i][j] = max(dis[i][j], min(tmp, dis[k][j]));
			else rep(j,1,k-1) dis[i][j] = max(dis[i][j], min(tmp, dis[k][j]));
		}
	} ans[m + 1] = n;
	pre(i,m,1) ans[i] += ans[i + 1];
	rep(i,1,m+1) cout << ans[i] << ' ' ;
}



P7518

欧艾大陆上有 \(n\) 座城市,城市从 \(1 \sim n\) 编号,所有城市经由 \(n - 1\) 条无向道路互相连通,即 \(n\) 座城市与 \(n - 1\) 条道路构成了一棵树。

每座城市的集市上都会出售宝石,总共有 \(m\) 种不同的宝石,用 \(1 \sim m\) 编号。\(i\) 号城市的集市出售的是第 \(w_i\) 种宝石,一种宝石可能会在多座城市的集市出售。

K 神有一个宝石收集器。这个宝石收集器能按照顺序收集至多 \(c\) 颗宝石,其收集宝石的顺序为:\(P_1, P_2, \ldots , P_c\)。更具体地,收集器需要先放入第 \(P_1\) 种宝石,然后才能再放入第 \(P_2\) 种宝石,之后再能放入第 \(P_3\) 种宝石,以此类推。其中 \(P_1, P_2, \ldots , P_c\) 互不相等。

K 神到达一个城市后,如果该城市的集市上出售的宝石种类和当前收集器中需要放入的种类相同,则他可以在该城市的集市上购买一颗宝石并放入宝石收集器中;否则他只会路过该城市什么都不做。

现在 K 神给了你 \(q\) 次询问,每次给出起点 \(s_i\) 与终点 \(t_i\),他想知道如果从 \(s_i\) 号城市出发,沿最短路线走到 \(t_i\) 号城市后,他的收集器中最多能收集到几个宝石?(在每次询问中,收集器内初始时没有任何宝石。起点与终点城市集市上的宝石可以尝试被收集)

\(1 \le n, q \le 2 \times {10}^5\)\(1 \le c \le m \le 5 \times {10}^4\)\(1 \le w_i \le m\)

设在取得第 \(v\) 种宝石后需要取得第 \(\text{next}(v)\) 种宝石。当 \(v = P_k\)\(\text{next}(v) = P_{k + 1}\),反之 \(\text{next}(v) = 0\)

首先套路地转化成 \(u\to \text{LCA} \to v\) 两段路径。

\(u\to \text{LCA}\) 是经典的,我们直接预处理每个点祖先中最深的 \(P_1\) 位置,并倍增预处理从某个点向祖先跳最多能拿多少颗宝石。这部分复杂度 \(O((n + q)\log n)\)
\(\text{LCA}\to v\) 就不太好做了,不容易处理出从 \(P\) 中任意位置和任意节点开始收集宝石的信息。

于是考虑离线。我们在 \(\text{LCA}\) 处挂一个询问 \(pos\),表示要从这个位置以及 \(P_{pos}\) 开始收集宝石,并在 \(t_i\) 处挂 \(i\) 询问结束的标志。
然后考虑 dfs。我们维护一系列集合 \(S_n\),初始 \(S_i = \{w_i\}\)。到达 \(u\) 节点后,我们把 \(w_u\) 所在集合和 \(\text{next}(w_u)\) 的集合合并。一次从 \(pos\) 开始的询问的答案是 \(P_{pos}\) 所在集合中在 \(P\) 序列中最靠后的元素。当 dfs 到 \(t_i\) 时我们回答这次询问即可。

不难发现可以用可撤销并查集维护这操作。

总时间复杂度 \(O((n + q) \log n)\)

怎么思路不难,调试这么恶心啊?

code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, m, c, q, p[N], suf[N], cid[N], w[N], t1, t2, ans[N];
vi g[N], ca[N]; vp qu[N];

int fa[N], son[N], siz[N], dep[N], top[N], buk[N], st[N][25], low1[N]; 
void dfs1(int u, int ft) {
	if (buk[suf[w[u]]]) st[u][0] = buk[suf[w[u]]];
	rep(t,1,23) st[u][t] = st[st[u][t - 1]][t - 1];
	int lst = buk[w[u]]; buk[w[u]] = u; 
	if (buk[p[1]]) low1[u] = buk[p[1]];
	fa[u] = ft, son[u] = 0, siz[u] = 1, dep[u] = dep[ft] + 1;
	for (auto v : g[u]) if (v != ft) {
		dfs1(v, u), siz[u] += siz[v];
		if (siz[v] > siz[son[u]]) son[u] = v;
	} buk[w[u]] = lst;
}
void dfs2(int u, int tp) {
	top[u] = tp;
	if (son[u]) dfs2(son[u], tp);
	for (auto v : g[u]) if (v != fa[u] and v != son[u])
		dfs2(v, v);
}

int lca(int u, int v) {
	while (top[u] != top[v]) {
		if (dep[top[u]] < dep[top[v]]) swap(u, v);
		u = fa[top[u]];
	} return dep[u] < dep[v] ? u : v;
}

int dsu[N], va[N], rt[N];
int find(int u) { return u == dsu[u] ? u : find(dsu[u]); }
void dfs3(int u) {
	for (auto [beg, id] : qu[u]) {
		dsu[id] = id, siz[id] = 1, va[id] = beg;
		if (rt[beg]) dsu[id] = rt[beg], ++ siz[rt[beg]];
		else rt[beg] = id;
	} 
	int rtu = rt[w[u]]; rt[w[u]] = 0;
	int nxt = suf[w[u]], rtnxt = rt[nxt];
	if (rtu) {
		if (!rtnxt) rt[nxt] = rtu, va[rtu] = nxt;
		else if (siz[rtu] < siz[rtnxt]) dsu[rtu] = rtnxt, siz[rtnxt] += siz[rtu];
		else dsu[rtnxt] = rt[nxt] = rtu, siz[rtu] += siz[rtnxt], va[rtu] = nxt;
	}
	for (auto v : ca[u]) {
		int id = va[find(v)];
		ans[v] = id ? cid[id] - 1 : c;
	}
	for (auto v : g[u]) if (v != fa[u]) dfs3(v);
	if (rtu) {
		if (!rtnxt) rt[nxt] = 0, va[rtu] = w[u];
		else if (rt[nxt] == rtnxt) dsu[rtu] = rtu, siz[rtnxt] -= siz[rtu];
		else dsu[rtnxt] = rt[nxt] = rtnxt, siz[rtu] -= siz[rtnxt], va[rtu] = w[u];
	}
	rt[w[u]] = rtu;
	for (auto [beg, id] : qu[u]) {
		if (rt[beg] == id) rt[beg] = 0;
		else -- siz[dsu[id]];
	} 
}

signed main() {
	cin >> n >> m >> c;
	rep(i,1,c) cin >> p[i];
	rep(i,1,c) suf[p[i]] = p[i + 1], cid[p[i]] = i;
	rep(i,1,n) cin >> w[i];
	rep(i,2,n) cin >> t1 >> t2, g[t1].eb(t2), g[t2].eb(t1);
	rep(i,1,n) reverse(g[i].begin(), g[i].end());
	dfs1(1, 0), dfs2(1, 1); memset(siz, 0, sizeof siz);
	cin >> q;
	rep(i,1,q) {
		cin >> t1 >> t2;
		int LCA = lca(t1, t2);
		if (dep[low1[t1]] <= dep[LCA]) {
			qu[LCA].eb(p[1], i), ca[t2].eb(i);
		} else {
			int edp = low1[t1];
			pre(j,23,0) if (dep[st[edp][j]] > dep[LCA]) edp = st[edp][j];
			edp = w[edp];
			if (suf[edp]) qu[LCA].eb(suf[edp], i), ca[t2].eb(i);
			else ans[i] = c;
		}
	} 
	dfs3(1);
	rep(i,1,q) cout << ans[i] << '\n';
}



P7519

封榜是 ICPC 系列竞赛中的一个特色机制。ICPC 竞赛是实时反馈提交结果的程序设计竞赛,参赛选手与场外观众可以通过排行榜实时查看每个参赛队伍的过题数与排名。竞赛的最后一小时会进行“封榜”,即排行榜上将隐藏最后一小时内的提交的结果。赛后通过滚榜环节将最后一小时的结果(即每只队伍最后一小时的过题数)公布。

Alice 围观了一场 ICPC 竞赛的滚榜环节。本次竞赛共有 \(n\) 支队伍参赛,队伍从 \(1 \sim n\) 编号,\(i\) 号队伍在封榜前通过的题数为 \(a_i\)。排行榜上队伍按照过题数从大到小进行排名,若两支队伍过题数相同,则编号小的队伍排名靠前。

滚榜时主办方以 \(b_i\) 不降的顺序依次公布了每支队伍在封榜后的过题数 \(b_i\)(最终该队伍总过题数为 \(a_i + b_i\)),并且每公布一支队伍的结果,排行榜上就会实时更新排名。Alice 并不记得队伍被公布的顺序,也不记得最终排行榜上的排名情况,只记得每次公布后,本次被公布结果的队伍都成为了新排行榜上的第一名,以及所有队伍在封榜后一共通过了 \(m\) 道题(即 \(\sum_{i = 1}^{n} b_i = m\))。

现在 Alice 想请你帮她算算,最终排行榜上队伍的排名情况可能有多少种。

\(1 \le n \le 13\)\(1 \le m \le 500\)\(0 \le a_i \le {10}^4\)

怎么 T2 < T1?

由于 \(b_i\) 不降,我们不妨考虑每次公布 \(b_i\) 后,\(b_j\text{ s.t. }j > i\) 都减 \(b_i\),这样每次决策的下界都是 \(0\),并且各决策的 \(a_i\) 的相对大小不变。

这样我们只需要设 \(f(S, i, \text{sum})\) 表示当前已经选择了集合 \(S\) 中元素,最后一个选择的是 \(i\),当前已知的 \(\sum_i b_i = \text{sum}\)。转移考虑枚举当前集合 \(S\),从 \(S\) 中找出一个元素 \(i\) 并剥离得到集合 \(S'\),并枚举 \(S'\) 中一个元素 \(j\),以及达到 \(S'\) 需要的 \(\sum_i b_i = k\)。我们能知道,最少需要 \(\max(a_j - a_i + [j < i], 0)\) 的量才能使 \(i\) 超过上一个队伍,因此 \(\sum_i b_i\) 的增量就是 \(n - |S| + 1\) 乘前面的值。写出转移就是

\[f\left(S, i, k + (n - |S| + 1) \times \max(a_j - a_i + [j < i], 0)\right) \leftarrow f(S', j, k) \]

初值类似地算即可。假设 \(a_k\) 为最大值,第一支被宣布的队伍是 \(i\),那当且仅当 \(n\times (a_k - a_i + [k < i]) \le m\)\(f\left(\{i\}, i, n\times (a_k - a_i + [k < i])\right) = 1\)

总时间复杂度 \(O(2^n n^2 m)\)

code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 15 + 10, M = 1 << 13 | 3;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, m, a[N], mxp, f[M][13][505];

signed main() {
	cin >> n >> m;
	rep(i,0,n - 1) {
		cin >> a[i]; 
		if (a[i] > a[mxp]) mxp = i;
	}
	rep(i,0,n - 1) if ((a[mxp] - a[i] + (mxp < i)) * n <= m) f[1 << i][i][(a[mxp] - a[i] + (mxp < i)) * n] = 1;
	rep(S,1,(1<<n)-1) {
		int _S = S, cnt = __builtin_popcount(S);
		if (cnt <= 1) continue;
		while (_S) {
			int i = __lg(_S & -_S); _S ^= _S & -_S;
			int s = S ^ (1 << i), _s = s;
			while (_s) {
				int j = __lg(_s & -_s); _s ^= _s & -_s;
				rep(k,0,m - (n - cnt + 1) * max(a[j] - a[i] + (j < i), 0)) {
					int w = k + (n - cnt + 1) * max(a[j] - a[i] + (j < i), 0);
					f[S][i][w] += f[s][j][k];
				}
			}
		}
	} 
	ll ans = 0;
	rep(i,0,n-1) rep(j,0,m) ans += f[(1 << n) - 1][i][j];
	cout << ans << '\n';
} 



P6623

给定一棵 \(n\) 个结点的有根树 \(T\),结点从 \(1\) 开始编号,根结点为 \(1\) 号结点,每个结点有一个正整数权值 \(v_i\)

\(x\) 号结点的子树内(包含 \(x\) 自身)的所有结点编号为 \(c_1,c_2,\dots,c_k\),定义 \(x\) 的价值为:

\[val(x)=(v_{c_1}+d(c_1,x)) \oplus (v_{c_2}+d(c_2,x)) \oplus \cdots \oplus (v_{c_k}+d(c_k, x)) \]

其中 \(d(x,y)\) 表示树上 \(x\) 号结点与 \(y\) 号结点间唯一简单路径所包含的边数,\(d(x,x) = 0\)\(\oplus\) 表示异或运算。

请你求出 \(\sum\limits_{i=1}^n val(i)\) 的结果。

\(1\leq n,v_i \leq 2^{19} + 722,1\leq p_i\leq n\)

哦,我还没打过从低到高插入的 Trie!

考虑这题如何暴力实现。我们 dfs 子树,并合并子树信息。也就是说,我们需要对每个点维护一个集合,支持全局 \(+1\)、插入元素、合并集合以及求全局异或和。
这是“高位在深” 01Trie 能维护的信息。也就是说,我们用 01Trie 结构来维护每个二进制数,但在插入时从低位到高位插入。

考虑这样的 01Trie 的性质。Trie 树合并和子树异或和都是经典信息,考虑全局 \(+1\) 所改变的情况。
我们自然能想到,在经典 01Trie 上给一个数 \(+1\),就是找到从这个数末尾向上、连续向左上走的一段,那我们只需要把这一段所有点和最浅点的父亲的子树翻转即可。由于低位和高位翻转了,在这样的 01Trie 上给一个数 \(+1\) 时,就需要翻转从根节点开始连续向右子树走的一段。对整体 \(+1\),我们就可以考虑从根节点开始递归求解。在点 \(u\) 时,首先将 \(u\) 的两棵子树翻转,随后若该点为某个数的结束点,则这个数的新结束点是 \(u\) 的右儿子,打上标记;最后向原来的右儿子、现在的左儿子递归即可。时间复杂度还是单次 \(O(\log n)\) 的。

这样我们就可以直接维护这题的信息了。总时间复杂度 \(O(n\log n)\)
类似题:P6018,只需要在每个点维护儿子信息即可。

code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 1 << 20 | 3;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, v[N], ft; ll ans;
vi g[N];

struct Trie { // 低先
	int tr[N * 20][2], ed[N * 20], cnt[N * 20], sum[N * 20], mlc, rt[N];

	inline void ps_p(int p) {
		cnt[p] = ed[p] ^ cnt[tr[p][0]] ^ cnt[tr[p][1]];
		sum[p] = (sum[tr[p][0]] << 1) ^ (sum[tr[p][1]] << 1 | cnt[tr[p][1]]);
	}

	void insert_(int &p, int va) {
		if (!p) p = ++ mlc;
		if (!va) return ed[p] ^= 1, cnt[p] ^= 1, void();
		insert_(tr[p][va & 1], va >> 1); ps_p(p);
	} inline void insert(int id, int va) { insert_(rt[id], va); }

	void add1_(int p) {
		if (!p) return;
		swap(tr[p][0], tr[p][1]); if (ed[p] and !tr[p][1]) tr[p][1] = ++ mlc;
		ed[tr[p][1]] ^= ed[p], cnt[tr[p][1]] ^= ed[p], ed[p] = 0;
		add1_(tr[p][0]); ps_p(p);
	} inline void add1(int id) { add1_(rt[id]); }

	int merge_(int x, int y) {
		if (!x or !y) return x | y;
		ed[x] ^= ed[y], cnt[x] ^= cnt[y], sum[x] ^= sum[y];
		tr[x][0] = merge_(tr[x][0], tr[y][0]);
		tr[x][1] = merge_(tr[x][1], tr[y][1]);
		ps_p(x);
		return x;
	} inline void merge(int id1, int id2) { rt[id1] = merge_(rt[id1], rt[id2]); }
} Tr;

void dfs(int u) {
	for (auto v : g[u]) dfs(v), Tr.merge(u, v);
	Tr.add1(u); Tr.insert(u, v[u]);
	ans += Tr.sum[Tr.rt[u]];
}

signed main() {
	cin >> n; 
	rep(i,1,n) cin >> v[i];
	rep(i,2,n) cin >> ft, g[ft].eb(i);
	dfs(1);
	cout << ans << '\n';
} 
posted @ 2023-03-26 11:08  joke3579  阅读(93)  评论(0编辑  收藏  举报