牛客练习赛142


A. 写轮眼

题意:一个数组,把每个数字的\(0\)变成\(8\)。求新数组的和减去原数组的和。

直接模拟每个数的变化就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	i64 ans = 0;
	for (int i = 0; i < n; ++ i) {
		int x;
		std::cin >> x;
		ans -= x;
		std::string s = std::to_string(x);

		int y = 0;
		for (auto & c : s) {
			int t = c - '0';
			if (t == 0) {
				y = y * 10 + 8;
			} else {
				y = y * 10 + t;
			}
			x /= 10;
		} 
		ans += y;
	}

	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	// std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

B. 移植

题意:\(f(x)\)\(x\)二进制\(1\)的个数,\(g(x)\)是最高位\(1\)下的\(0\)的个数加一。一直使得\(x = g(f(x))\)。能否有一个数操作后永远不会变化。

猜测没有无解的情况。
然后发现操作后会变得很小,因为只有\(64\)位。直接模拟操作就行了。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	i64 x;
	std::cin >> x;
	auto f = [&](i64 x) -> i64 {
		if (x == 0) {
			return 0;
		}

		int lg = std::__lg(x);
		int res = 0;
		for (int i = 0; i <= lg; ++ i) {
			res += x >> i & 1;
		}

		return res;
	};


	auto g = [&](i64 x) -> i64 {
		if (x == 0) {
			return 1;
		}

		int lg = std::__lg(x);
		int res = 0;
		for (int i = 0; i <= lg; ++ i) {
			res += x >> i & 1;
		}

		return lg + 1 - res + 1;
	};

	while (1) {
		i64 y = g(f(x));
		if (x == y) {
			break;
		}
		x = y;
	}
	std::cout << x << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	// std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

C. 战前准备

题意:给你一个数组\(a\),都在\([0, m)\)之间,记\(p_i\)\(i\)最前的位置,求左移\([0, n)\)次中有多少次都有\(p_i < p_{i+1}\)

\(set\)记录每个数出现的位置。
然后直接模拟左移。每次要把\(a_i\)移到后面,我们设后面还有\(n\)个空间,那么就是每次把一个数拿到后面就行了,于是把\(i\)这个位置从\(a_i\)的位置集合里删掉,并且加入\(n+i\)
然后需要记录一个\(cnt\)代表有多少\(p_i < p_{i+1}\)。发现每次只和\((a_i - 1, a_i), (a_i, a_i + 1)\)有关,于是可以计算其改变的贡献。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	int ans = 0;
	std::vector<std::set<int>> s(m);
	for (int i = 0; i < n; ++ i) {
		s[a[i]].insert(i);
	}

	int cnt = 0;
	for (int i = 0; i + 1 < m; ++ i) {
		cnt += *s[i].begin() < *s[i + 1].begin();
	}

	ans += cnt == m - 1;
	for (int i = 0; i + 1 < n; ++ i) {
		if (a[i] + 1 < m) {
			cnt -= *s[a[i]].begin() < *s[a[i] + 1].begin();
		}
		if (a[i] > 0) {
			cnt -= *s[a[i] - 1].begin() < *s[a[i]].begin();
		}

		s[a[i]].erase(s[a[i]].begin());
		s[a[i]].insert(n + i);
		if (a[i] + 1 < m) {
			cnt += *s[a[i]].begin() < *s[a[i] + 1].begin();
		}
		if (a[i] > 0) {
			cnt += *s[a[i] - 1].begin() < *s[a[i]].begin();
		}
		ans += cnt == m - 1;
	}
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	// std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

D. 第四次忍界大战

题意:一棵树每个点有权值。求有多少路径使得起点终点相同,且中间的点都大于起点终点。

从大到小加入点。
设当前加入值为\(x\)的所有点。那么比\(x\)大的点已经构成了若干个联通块,枚举值为\(x\)的点的所有出边,然后记录比它的的点所在的联通块已经加入了几个可以和它贡献的点,就可以知道这个点与这些点的贡献。只需要记录个数就行了,因为如果有两个值为\(x\)的点在一起,它们是互相访问不到对方的联通块的,而我们只考虑比\(x\)大的点的联通块。
然后把值为\(x\)的点加入联通块即可。可以用并查集维护。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

struct DSU {
	std::vector<int> fa, cnt;
	DSU();
	DSU(int n) {
		init(n);
	}

	void init(int n) {
		fa.assign(n + 1, 0);
		cnt.assign(n + 1, 1);
		std::ranges::iota(fa, 0);
	}

	int find(int x) {
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}

	bool merge(int x, int y) {
		int u = find(x), v = find(y);
		if (u == v) {
			return false;
		}

		fa[v] = u;
		cnt[u] += cnt[v];
		return true;
	}

	bool same(int u, int v) {
		return find(u) == find(v);
	}

	int size(int u) {
		return cnt[find(u)];
	}
};

void solve() {
	int n;
	std::cin >> n;
	std::vector<std::vector<int>> adj(n);
	std::vector<int> a(n);
	std::map<int, std::vector<int>> b;
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];	
		b[-a[i]].push_back(i);
	}

	for (int i = 1; i < n; ++ i) {
		int u, v;
		std::cin >> u >> v;
		-- u, -- v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}

	DSU dsu(n);
	i64 ans = 0;
	for (auto & [_, A] : b) {
		std::map<int, int> cnt;
		for (auto & u : A) {
			for (auto & v : adj[u]) {
				if (a[v] > a[u]) {
					ans += cnt[dsu.find(v)];
					cnt[dsu.find(v)] += 1;
				}
			}
		}

		for (auto & u : A) {
			for (auto & v : adj[u]) {
				if (a[v] >= a[u]) {
					dsu.merge(u, v);
				}
			}
		}
	}	

	ans *= 2;
	for (int i = 0; i < n; ++ i) {
		for (auto & j : adj[i]) {
			ans += a[i] == a[j];
		}
	}

	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	// std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

G. 秽土转生解

题意:给你一个排列\(p\),构造一个排列\(q\)。使得\(p, q\)逆序对相等且\(p \ne q\)\(q\)字典序最小。

考虑模拟。对于第\(i\)个位置,后面最多贡献\(max = \frac{(n-i)\times (n-i-1)}{2}\)个逆序对,那么记目前还需要\(cnt\)个逆序对,我们如果选剩下的点里第\(k\)大的点,这个点就与后面贡献\(k - 1\)个逆序对。显然我们希望满足\(max + k - 1 \geq cnt\)\(k\)最小。可以用线段树二分。记录\([1, n]\)的前缀和,每个点的值初始为\(1\),然后找前缀和大于等于\(k\)的最前的前缀位置,就可以知道应该选哪个数。如此模拟即可。
但还有一个问题,就是如果\(p\)是字典序最小的怎么办?我们只需要找到最后一个\(q_i < q_{i+1}\)的位置,把这个位置选更大的一个数,然后重新模拟一下这个后缀就行了。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

template <class T>
struct Fenwick {
    int n;
    std::vector<T> tr;

    Fenwick(int _n) {
        init(_n);
    }

    void init(int _n) {
        n = _n;
        tr.assign(_n + 1, T{});
    }

    void add(int x, const T &v) {
        for (int i = x; i <= n; i += i & -i) {
            tr[i] = tr[i] + v;
        }
    }

    T query(int x) {
        T res{};
        for (int i = x; i; i -= i & -i) {
            res = res + tr[i];
        }
        return res;
    }

    T sum(int l, int r) {
        return query(r) - query(l - 1);
    }
};

template <class Info>
struct SegmentTree {
	struct Node {
		int l, r;
		Info info;
	};

	std::vector<Node> tr;

	SegmentTree() {};
	SegmentTree(int n) {
		init(n);
	}

	SegmentTree(std::vector<Info> & info) {
		init(info);
	}

	void init(int n) {
		tr.assign(n << 2, {});
		build(0, n - 1);
	}

	void init(std::vector<Info> & info) {
		int n = info.size();
		tr.assign(n << 2, {});
		std::function<void(int, int, int)> build = [&](int l, int r, int u) -> void {
			tr[u] = {l, r, {}};
			if (l == r) {
				tr[u].info = info[l];
				return;
			}

			int mid = l + r >> 1;
			build(l, mid, u << 1); build(mid + 1, r, u << 1 | 1);
			pushup(u);
		};

		build(0, n - 1, 1);
	}

	void pushup(int u) {
		tr[u].info = tr[u << 1].info + tr[u << 1 | 1].info;
	}

	void build(int l, int r, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, u << 1); build(mid + 1, r, u << 1 | 1);
		pushup(u);
	}

	void modify(int p, const Info & info, bool set = false) {
		int u = 1;
		while (tr[u].l != tr[u].r) {
			int mid = tr[u].l + tr[u].r >> 1;
			if (p <= mid) {
				u = u << 1;
			} else {
				u = u << 1 | 1;
			}
		}

		if (set) {
			tr[u].info = info;
		} else {
			tr[u].info = tr[u].info + info;
		}

		u >>= 1;
		while (u) {
			pushup(u);
			u >>= 1;
		}
	}

	Info query(int l, int r, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			return tr[u].info;
		}

		int mid = tr[u].l + tr[u].r >> 1;
		if (r <= mid) {
			return query(l, r, u << 1);
		} else if (l > mid) {
			return query(l, r, u << 1 | 1);
		}

		return query(l, r, u << 1) + query(l, r, u << 1 | 1);
	}

	int query_geq(i64 x) {
		if (x > tr[1].info.sum) {
			return -1;
		}

		int u = 1;
		while (tr[u].l != tr[u].r) {
			if (tr[u << 1].info.sum >= x) {
				u = u << 1;
			} else {
				x -= tr[u << 1].info.sum;
				u = u << 1 | 1;
			}
		}

		return tr[u].l;
	}
};

struct Info {
	int sum = 1;
};

Info operator + (const Info & l, const Info & r) {
	Info res{};
	res.sum = l.sum + r.sum;
	return res;
}

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	i64 cnt = 0;
	Fenwick<int> tr1(n);
	for (int i = 0; i < n; ++ i) {
		cnt += tr1.sum(a[i], n);
		tr1.add(a[i], 1);
	}

	if (cnt == 0 || cnt == (i64)n * (n - 1) / 2) {
		std::cout << -1 << "\n";
		return;
	}

	std::vector<int> p(n);
	SegmentTree<Info> tr(n);

	std::vector<i64> cnt1(n + 1);
	auto dfs = [&](auto & self, int u, i64 cnt) -> bool {
		if (u > n) {
			return cnt == 0;
		}

		cnt1[u] = cnt;
		
		i64 max = (i64)(n - u) * (n - u - 1) / 2;
		int x = tr.query_geq(std::max(0ll, cnt - max) + 1);
		if (x == -1) {
			return false;
		}

		p[u - 1] = x + 1;
		tr.modify(x, Info{-1});
		return self(self, u + 1, std::min(max, cnt));
	};

	if (dfs(dfs, 1, cnt)) {
		if (p == a) {
			cnt = 0;
			for (int i = n - 2; i >= 0; -- i) {
				tr.modify(p[i + 1] - 1, Info{1});
				if (a[i] < a[i + 1]) {
					tr.modify(p[i] - 1, Info{1});
					i64 max = (i64)(n - (i + 1)) * (n - (i + 1) - 1) / 2;
					int x = tr.query_geq(std::max(0ll, cnt1[i + 1] - max) + 2);
					tr.modify(x, Info{-1});
					if (x == -1) {
						std::cout << -1 << "\n";
						return;
					}
					p[i] = x + 1;
					if (!dfs(dfs, i + 2, std::min(max, cnt1[i + 1]) - 1)) {
						std::cout << -1 << "\n";
						return;
					}
					break;
				}
			}
		}
		
		for (int i = 0; i < n; ++ i) {
			std::cout << p[i] << " \n"[i == n - 1];
		}
	} else {
		std::cout << -1 << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	// std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}
posted @ 2025-07-11 21:38  maburb  阅读(87)  评论(0)    收藏  举报