The 2018 ACM-ICPC China JiangSu Provincial Programming Contest (held by ChinaUniversity of Mining and Technology)


A. Plague Inc

多源\(bfs\)模板。

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

using i64 = long long;

void solve() {
	int n, m;
	while (std::cin >> n >> m) {
		int k;
		std::cin >> k;
		std::vector<std::vector<int>> d(n, std::vector<int>(m, -1));
		std::queue<std::pair<int, int>> q;
		for (int i = 0; i < k; ++ i) {
			int x, y;
			std::cin >> x >> y;
			-- x, -- y;
			q.emplace(x, y);
			d[x][y] = 0;
		}

		const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
		while (q.size()) {
			auto [x, y] = q.front(); q.pop();
			for (int i = 0; i < 4; ++ i) {
				int nx = x + dx[i], ny = y + dy[i];
				if (nx < 0 || nx >= n || ny < 0 || ny >= m || d[nx][ny] != -1) {
					continue;
				}

				d[nx][ny] = d[x][y] + 1;
				q.emplace(nx, ny);
			}
		}

		int ansx = -1, ansy = -1, max = -1;
		for (int i = 0; i < n; ++ i) {
			for (int j = 0; j < m; ++ j) {
				if (d[i][j] > max) {
					ansx = i, ansy = j;
					max = d[i][j];
				}
			}
		}

		std::cout << ansx + 1 << " " << ansy + 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;
}

B. Array

题意:求长度为\(n\)\(k\)个逆序对的排列有多少个。

考虑\(dp\)\(f[i][j]\)表示长度为\([1, i]\)的排列逆序对有\(k\)个的个数,考虑\(i\)怎么放,发现\(i\)放在几个数前面就多几个逆序对。那么\(f[i][j] = \sum_{k=0}^{i-1} f[i - 1][j - k]\)。前缀和优化到\(O(n^2)\)。不能开二维数组,所以需要滚动数组优化,然后需要把询问离线。

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

using i64 = long long;

const int mod = 1e9 + 7;
const int N = 5010;

int f[N], sum[N];

void solve() {
	std::vector<std::array<int, 3>> q;
	int n, k, id = 0;
	while (std::cin >> n >> k) {
		q.push_back(std::array<int, 3>{n, k, id ++ });
	}

	int m = q.size();
	std::vector<int> ans(m);
	std::sort(q.begin(), q.end());
	f[0] = 1;
	for (int i = 1, t = 0; i <= 5000; ++ i) {
		while (t < m && q[t][0] == i && q[t][1] == 0) {
			ans[q[t][2]] = 1;
			++ t;
		}

		for (int j = 1; j <= 5000; ++ j) {
			f[j] = j < i ? sum[j] : (sum[j] - sum[j - i] + mod) % mod;
			while (t < m && q[t][0] == i && q[t][1] == j) {
				ans[q[t][2]] = f[j];
				++ t;
			}
		}

		sum[0] = 1;
		for (int j = 1; j <= 5000; ++ j) {
			sum[j] = (sum[j - 1] + f[j]) % mod;
		}
	}


	for (int i = 0; i < m; ++ i) {
		std::cout << ans[i] << "\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. Persona5

多重排列模板题。

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

using i64 = long long;

const int N = 1000010, mod = 1e9 + 7;

int fact[N], infact[N];
int a[N];

int power(int a, int b) {
	int res = 1;
	for (;b;b >>= 1, a = 1ll * a * a % mod) {
		if (b & 1) {
			res = 1ll * res * a % mod;
		}
	}
	return res;
}

void solve() {
	fact[0] = infact[0] = 1;
	for (int i = 1; i < N; ++ i) {
		fact[i] = (i64)i * fact[i - 1] % mod;
	}

	infact[N - 1] = power(fact[N - 1], mod - 2);
	for (int i = N - 1; i > 1; -- i) {
		infact[i - 1] = (i64)i * infact[i] % mod;
	}

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

		int ans = fact[sum];
		for (int i = 0; i < n; ++ i) {
			ans = (i64)ans * infact[a[i]] % mod;
		}
		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;
}

E. Massage

题意:从\((1, 1)\)走到\((n, m)\)走两次,只能向下或向右走,每个格子只能走一次。求方案数。

一点点容斥。
实际可以看作两个起点:\((1, 2), (2, 1)\)。路径显然是\((1, 2)\)->\((n - 1, m), (2, 1)\)-> \((n, m - 1)\)。这两个都可以用组合数算。但还是会有重复走的格子,发现如果重复,一定是从\((1, 2)\)走到\((n, m - 1)\)和从\((2, 1)\)走到\((n - 1, m)\)。减去这个方案数就行。

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

using i64 = long long;

const int N = 1010, mod = 1e9 + 7;

int fact[N], infact[N];

int power(int a, int b) {
	int res = 1;
	for (;b;b >>= 1, a = 1ll * a * a % mod) {
		if (b & 1) {
			res = 1ll * res * a % mod;
		}
	}
	return res;
}

void solve() {
	fact[0] = infact[0] = 1;
	for (int i = 1; i < N; ++ i) {
		fact[i] = (i64)i * fact[i - 1] % mod;
	}

	infact[N - 1] = power(fact[N - 1], mod - 2);
	for (int i = N - 1; i > 1; -- i) {
		infact[i - 1] = (i64)i * infact[i] % mod;
	}

	auto C = [&](int n, int m) -> int {
		if (n < m || m < 0) {
			return 0;
		}

		return (i64)fact[n] * infact[m] % mod * infact[n - m] % mod;
	};

	int n, m;
	while (std::cin >> n >> m) {
		int ans = (i64)C(n + m - 4, n - 2) * C(n + m - 4, n - 2) % mod;
		ans = (ans - (i64)C(n + m - 4, n - 1) * C(n + m - 4, n - 3) % mod + mod) % mod;
		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;
}

F. Company

题意:给你一个森林,每个点有点权\(a_i\)。求有多少三元组\((i, j, k)\)。满足\(i\)\(j\)的祖先,\(j\)\(k\)祖先,且\(a_j > a_i, a_j > a_k\)

对于每个点,求出祖先节点有多少个小于它的,子树里有多少个小于它的,然后乘起来就是这个点作为\(j\)贡献的答案。
求祖先节点小于它的数直接\(dfs\)加树状数组就行。求子树有多少小于它的,可以用\(dsu\ on\ tree\),也就是树上启发式合并。

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

using i64 = long long;

struct Fenwick {
	std::vector<int> tr;
	int n;
	Fenwick(){};
	Fenwick(int _n) : n(_n) {
		init(n);
	}

	void init(int n) {
		tr.assign(n + 1, 0);
	}

	void clear() {
		for (int i = 0; i <= n; ++ i) {
			tr[i] = 0;
		}
	}

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

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

		return res;
	}

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

void solve() {
	int n;
	while (std::cin >> n) {
		std::vector<std::vector<int>> adj(n);
		std::vector<int> fa(n);
		for (int i = 0; i < n; ++ i) {
			std::cin >> fa[i];
			-- fa[i];
			if (fa[i] >= 0) {
				adj[fa[i]].push_back(i);
			}
		}

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

		std::vector<int> size(n), son(n, -1);
		auto dfs = [&](auto & self, int u) -> void {
			size[u] = 1;
			for (auto & v : adj[u]) {
				self(self, v);
				size[u] += size[v];
				if (son[u] == -1 || size[v] > size[son[u]]) {
					son[u] = v;
				}
			}
		};

		int Son = -1;
		auto update = [&](auto & self, int u, int add) -> void {
			if (u == Son) {
				return;
			}

			tr.add(a[u], add);
			for (auto & v : adj[u]) {
				self(self, v, add);
			}
		};

		i64 ans = 0;
		auto dsu = [&](auto & self, int u, bool del) -> void {
			int cnt = tr.query(a[u] - 1);
			tr.add(a[u], 1);
			for (auto & v : adj[u]) {
				if (v == son[u]) {
					continue;
				}
				self(self, v, true);
			}

			if (son[u] != -1) {
				self(self, son[u], false);
				Son = son[u]; 
			} else {
				Son = -1;
			}
			tr.add(a[u], -1);

			update(update, u, 1);

			ans += (i64)cnt * (tr.query(a[u] - 1) - cnt);
			if (del) {
				Son = -1;
				update(update, u, -1);
			}
		};

		for (int i = 0; i < n; ++ i) {
			if (fa[i] == -1) {
				tr.clear();
				dfs(dfs, i);
				dsu(dsu, i, true);
			}
		}

		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. Window

题意:求数组\(a\)的所有长度为\(m\)的子数组的元素乘积对\(p\)取模后的和。和不用取模,\(p\)不一定是质数。

看起来是数学题。其实不用管\(p\)是不是质数,直接用线段树维护即可。

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

using i64 = long long;

struct SegmentTree {
	struct Node {
		int l, r;
		int sum;
	};

	std::vector<Node> tr;
	int P;

	SegmentTree(){};
	SegmentTree(std::vector<int> & a, int p) {
		int n = a.size();
		P = p;
		tr.assign(n << 2, {});
		auto build = [&](auto & self, int l, int r, int u = 1) -> void {
			tr[u] = {l, r};
			if (l == r) {
				tr[u].sum = a[l];
				return;
			}

			int mid = l + r >> 1;
			self(self, l, mid, u << 1);
			self(self, mid + 1, r, u << 1 | 1);
			tr[u].sum = (i64)tr[u << 1].sum * tr[u << 1 | 1].sum % P;
		};

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

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

		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 (i64)query(l, r, u << 1) * query(l, r, u << 1 | 1) % P;
	}
};

void solve() {
	int n, m, p;
	while (std::cin >> n >> m >> p) {
		std::vector<int> a(n);
		int x, y, z;
		std::cin >> a[0] >> x >> y >> z;
		a[0] %= p;
		for (int i = 1; i < n; ++ i) {
			a[i] = ((i64)x * a[i - 1] % p * a[i - 1] % p + (i64)y * a[i - 1] % p + z) % p;
		}

		if (p == 1) {
			std::cout << 0 << "\n";
			continue;
		}

		SegmentTree tr(a, p);
		i64 ans = 0;
		for (int i = 0; i + m - 1 < n; ++ i) {
			ans += tr.query(i, i + 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;
}

J. set

题意:\([1, n]\)的每个子集的价值为每个数的乘积的平方,合法子集是指没有两个相邻的数的子集。求所有合法子集的和。

\(f[i][0/1]\)表示前\(i\)个数组成的集合放不放\(i\)的价值,那么\(f[i][1] = i^2 \times f[i - 1][0], f[i][0] = f[i - 1][0] + f[i - 1][1]\)。需要使用高精度。

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

using i64 = long long;

struct Int {
	std::vector<int> a;
	Int() {
		a.assign(1, 0);
	}

	Int(int n) {
		do {
			a.push_back(n % 10);
			n /= 10;
		} while (n);
	}

	int & operator[](int i) {
		return a[i];
	}

	int size() {
		return (int)a.size();
	}
};

Int operator + (Int & a, Int & b) {
	Int res;
	int n = a.size(), m = b.size();
	res.a.assign(std::max(n, m) + 1, 0);
	for (int i = 0, t = 0; i < n || i < m || t; ++ i) {
		if (i < n) {
			t += a[i];
		}

		if (i < m) {
			t += b[i];
		}

		res[i] = t % 10;
		t /= 10;
	}

	while (res.size() > 1 && res.a.back() == 0) {
		res.a.pop_back();
	}

	return res;
}

Int operator * (Int & a, int b) {
	Int res;
	int n = a.size();
	res.a.assign(n + 10, 0);
	for (int i = 0, t = 0; i < n || t; ++ i) {
		if (i < n) {
			t += a[i] * b;
		}
		res[i] = t % 10;
		t /= 10;
	}

	while (res.size() > 1 && res.a.back() == 0) {
		res.a.pop_back();
	}
	return res;
}

Int sub(Int a) {
	int n = a.size();
	for (int i = 0; i < n; ++ i) {
		if (a[i] == 0) {
			a[i] = 9;
		} else {
			-- a[i];
			break;
		}
	}

	while (a.size() > 1 && a.a.back() == 0) {
		a.a.pop_back();
	}

	return a;
}

std::ostream & operator << (std::ostream & out, const Int & a) {
	for (int i = (int)a.a.size() - 1; i >= 0; -- i) {
		out << a.a[i];
	}
	return out;
}

const int N = 110;

Int f[N][2];

void solve() {
	f[0][0] = 1;
	for (int i = 1; i < N; ++ i) {
		f[i][1] = f[i - 1][0] * (i * i);
		f[i][0] = f[i - 1][0] + f[i - 1][1];
	}
	
	int n;
	while (std::cin >> n) {
		std::cout << sub(f[n][0] + f[n][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;
}

I. T-shirt

题意:有\(n\)天和\(m\)件衣服。如果某天穿第\(i\)件衣服,第二天穿第\(j\)件衣服,就会有\(f[i][j]\)的价值。给出\(f\)。求一个价值最大的方案。

我们把两个点看作一个点,第一天无法组成一个点所以价值为零,从第二天开始,\((i, j)\)这个点表示从\(i\)\(j\)的价值。那么我们需要选一条长度为\(n-1\)的路径,每个点可以重复经过,使得经过的点的点权的和最大。
那么这是矩阵乘法经典问题。记\(dp[t][i][j]\)为第\(t\)天走到\((i, j)\)这个点的最大价值。有\(dp[t + 1][i][j] = \max_{k=1}^{m} dp[t][i][k] + dp[t][k][j]\)。于是做矩阵乘法就行。

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

using i64 = long long;

const int N = 100;
using Mat = std::array<std::array<i64, N>, N>;

void clear(Mat & m) {
	for (int i = 0; i < N; ++ i) {
		for (int j = 0; j < N; ++ j) {
			m[i][j] = 0;
		}
	}
}

Mat operator * (const Mat & a, const Mat & b) {
	static Mat res;
	clear(res);
	for (int k = 0; k < N; ++ k) {
		for (int i = 0; i < N; ++ i) {
			for (int j = 0; j < N; ++ j) {
				res[i][j] = std::max(res[i][j], a[i][k] + b[k][j]);
			}
		}
	}

	return res;
}

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

		-- n;
		clear(res);
		for(;n; n >>= 1, a = a * a) {
			if (n & 1) {
				res = res * a;
			}
		}

		i64 ans = 0;
		for (int i = 0; i < m; ++ i) {
			for (int j = 0; j < m; ++ j) {
				ans = std::max(ans, res[i][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;
}

K. Road

题意:给你一个图,删边删到只剩一棵树。使得每个点到起点的最短距离不变。求方案数。

求出每个点有多少个点到它的距离是最短距离,那么就是选一个保留不删,方案数就是这些点的数量。每个点的方案数乘起来就是答案。

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

using i64 = long long;

const int mod = 1e9 + 7;

void solve() {
	int n;
	while (std::cin >> n) {
		std::vector<std::vector<std::pair<int, int>>> adj(n);
		for (int i = 0; i < n; ++ i) {
			std::string s;
			std::cin >> s;
			for (int j = 0; j < n; ++ j) {
				if (s[j] != '0') {
					adj[i].emplace_back(j, s[j] - '0');
				}
			}
		}

		using PII = std::pair<int, int>;
		std::priority_queue<PII, std::vector<PII>, std::greater<PII>> heap;
		const int inf = 1e9;
		std::vector<int> dist(n, inf), cnt(n, 0);
		heap.emplace(0, 0);
		dist[0] = 0;
		cnt[0] = 1;
		while (heap.size()) {
			auto [d, u] = heap.top(); heap.pop();
			if (d != dist[u]) {
				continue;
			}

			for (auto & [v, w] : adj[u]) {
				if (dist[v] > d + w) {
					dist[v] = d + w;
					cnt[v] = 1;
					heap.emplace(dist[v], v);
				} else if (dist[v] == d + w) {
					++ cnt[v];
				}
			}
		}

		int ans = 1;
		for (int i = 0; i < n; ++ i) {
			ans = (i64)ans * cnt[i] % mod;
		}

		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;
}
posted @ 2025-05-12 19:17  maburb  阅读(10)  评论(0)    收藏  举报