2024年国际大学生程序设计竞赛(ACM-ICPC)新疆赛区大赛

2024年国际大学生程序设计竞赛(ACM-ICPC)新疆赛区大赛

A

观察到 n8 直接暴力 dfs 或者用 next_permutation 即可。

Code

#include <bits/stdc++.h>

typedef long long LL;


int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n, k;
	std::cin >> n >> k;

	std::vector<int> p(n);
	std::iota(p.begin(), p.end(), 0);

	std::vector<int> a(n);

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

	int ans = 0;
	do {
		bool ok = true;
		for (int i = 0; i < n; i++) {
			if (i && std::abs(a[p[i]] - a[p[i - 1]]) < k) {
				ok = false;
				break;
			}
		}
		if (ok) {
			ans++;
		}
	} while (std::next_permutation(p.begin(), p.end()));


	std::cout << ans << '\n';

	return 0;
}

B

首先观察到这棵树是一个满三叉树,对于删除一个子树 u,我们可以计算其子树内有多少个点,这里可以先算出 u 的深度,那么以 u 为根的子树的大小就为:(3ndep+11)/2

然后注意到,如果以 u 为根的子树已经被删除了,那么其所有祖先的子树都要扣除这部分的贡献,即我们要沿着 ufau...1 这个链,把其祖先的子树要扣除吊这次删除的数量。

同时注意,如果一个点 u 的祖先曾经被删除过,那么就可以忽略吊 u 的这部分操作。

复杂度是 O(mn),常数有点大,建议用哈希表,如果用 map 还会多个 log

Code

#include <bits/stdc++.h>

typedef long long LL;



int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n, m;
	std::cin >> n >> m;

	std::unordered_map<LL, LL> mp;
	std::unordered_set<LL> se;

	auto calc = [&](LL k) {
		LL cnt = 0, v = 1;
		int dep = 0;
		while (cnt < k) {
			cnt += v;
			v *= 3;
			dep++;
		}
		LL sum = 0;
		v = 1;
		for (int i = 1; i <= n - dep + 1; i++) {
			sum += v;
			v *= 3;
		}
		return sum;
	};
	LL tot = 0, v = 1;
	for (int i = 1; i <= n; i++) {
		tot += v;
		v *= 3;
	}
	while (m--) {
		LL k;
		std::cin >> k;
		LL nk = k;
		bool ok = false;
		while (k) {
			if (se.count(k)) {
				ok = true;
				break;
			}
			if (k % 3 == 0) k /= 3;
			else if (k % 3 == 1) k = (k - 1) / 3;
			else k = (k + 1) / 3;
		}
		if (ok) {
			std::cout << tot << '\n';
			continue;
		}
		k = nk;
		se.insert(k);
		LL cnt = calc(k) - mp[k];
		tot -= cnt;
		std::cout << tot << '\n';
		while (k) {
			mp[k] += cnt;
			if (k % 3 == 0) k /= 3;
			else if (k % 3 == 1) k = (k - 1) / 3;
			else k = (k + 1) / 3;
		}
	}	

	return 0;
}

C

观察到对于一个完全平方数 z,以及一个数 x,那么说明 xzx 不能再一个集合里面。

这是非常经典的二分图问题,我们考虑 xzx 连边,那么问题等价于判断该图是不是一个二分图。

由于值域 2×105,所以我们枚举完全平方数是根号级别的。

时间复杂度为 O(nn)

判断二分图我们可以用 dfs 染色法,或者扩展域并查集。

Code

#include <bits/stdc++.h>

typedef long long LL;

const int N = 2e5 + 10, M = 2e5;
bool st[N];
int a[N], p[2 * N];

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

void solve() {
	int n;
	std::cin >> n;
	for (int i = 0; i < n; i++) {
		std::cin >> a[i];
		st[a[i]] = true;
	}
	for (int i = 1; i <= 2 * M; i++) {
		p[i] = i;
	}
	for (int i = 0; i < n; i++) {
		for (int j = (int)sqrt(a[i]) + 1; j * j - a[i] <= M; j++) {
			if (st[j * j - a[i]] && j * j - a[i] != a[i]) {
				p[find(a[i])] = find(j * j - a[i] + M);
				p[find(j * j - a[i])] = find(a[i] + M);
			}
		}
	}
	for (int i = 0; i < n; i++) {
		st[a[i]] = false;
	}
	for (int i = 0; i < n; i++) {
		if (find(a[i]) == find(a[i] + M)) {
			std::cout << "NO\n";
			return;
		}
	}
	std::cout << "YES\n";
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int t;
	std::cin >> t;
	while (t--) {
		solve();
	}	

	return 0;
}

D

观察到如下性质:

  • 合并的顺序,不影响最终获得的能量。

所以如果用了操作之后的序列为:(a1,b1),(a2,b2),...,(an,bn)

那么其最终获得的能量一定是固定的:a1b2+a2b3+...+an1bn

现在我们考虑区间 [l,r] 用了操作,使得其 ai,bi 都乘上了 k

不难想到前缀和,我们可以考虑定义如下:

  • di=aibi+1
  • prei=d1+d2+..+di
  • sufi=di+di+1+..+dn

那么我们枚举右端点 r,问题相当于求所有 1lr

k2×(prer1prel1)+sufi+(k1)×dr+prei1+(k1)×dl1

不难发现只要维护 prei1+(k1)×dl1k2×prel1 的最大值即可,显然是一个前缀最大值。

Code

#include <bits/stdc++.h>

typedef long long LL;


int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n, k;
	std::cin >> n >> k;

	std::vector<LL> d(n + 1, 0), a(n + 1), b(n + 1);
	LL ans = -1e18;
	for (int i = 1; i <= n; i++) {
		std::cin >> a[i] >> b[i];
		if (i > 1) {
			d[i - 1] = a[i - 1] * b[i];
		}
	}

	std::vector<LL> suf(n + 1, 0);

	for (int i = n - 1; i >= 1; i--) {
		suf[i] = suf[i + 1] + d[i];
	}

	LL pre = 0, maxv = 0;
	for (int i = 1; i <= n; i++) {
		ans = std::max(ans, pre * k * k + maxv + suf[i] + d[i] * (k - 1));
		pre += d[i];
		maxv = std::max(maxv, pre + d[i] * (k - 1) - k * k * pre);
	}

	std::cout << ans << '\n';

	return 0;
}

E

比较裸的 djstla,在松弛的时候判断绿,黄,红灯的变换,来判断是否要停下,注意到终点了就不用在通过交通灯了。

Code

#include <bits/stdc++.h>

typedef long long LL;
typedef std::pair<int, int> PII;

const int N = 1e4 + 10, INF = 1e9;

int d[N];
bool st[N];

std::vector<std::pair<int, int>> g[N];

int a[N][3];


int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n, m, s, e;
	while (std::cin >> n >> m >> s >> e, n || m || s || e) {
		for (int i = 0; i < n; i++) {
			g[i].clear();
			for (int j = 0; j < 3; j++) {
				std::cin >> a[i][j];
			}
		}

		while (m--) {
			int u, v, w;
			std::cin >> u >> v >> w;
			g[u].push_back({v, w});
			g[v].push_back({u, w});
		}

		for (int i = 0; i < n; i++) {
			d[i] = INF;
			st[i] = false;
		}
		std::priority_queue<PII, std::vector<PII>, std::greater<PII>> heap;
		d[s] = 5;
		heap.push({d[s], s});

		while (heap.size()) {
			int u = heap.top().second;
			heap.pop();
			if (st[u]) continue;
			st[u] = true;
			for (auto [v, w] : g[u]) {
				int t = (d[u] + w) % (a[v][0] + a[v][1] + a[v][2]);
				int tis = 0;
				if (t >= a[v][0] + a[v][1]) {
					tis += a[v][0] + a[v][1] + a[v][2] - t + 5;
				}
				if (v == e) tis = 0;
				if (d[v] > d[u] + w + tis) {
					d[v] = d[u] + w + tis;
					heap.push({d[v], v});
				}
			}
		}

		std::cout << d[e] / 60 << ":";
		if (d[e] % 60 < 10) std::cout << 0;
		std::cout << d[e] % 60 << "\n";
	}

	

	return 0;
}

F

观察到,最后满足 ai=i 位置构成的序列必然是原先序列的一个严格递增子序列。

可以证明只要选出的子序列是严格递增的,我们一定可以通过操作使这些位置都满足条件。

我们可以先把除了选出来的数的其他数删了。

例如我们删完后为:2,4,7,8

那么我们显然可以通过插入 0 使其满足条件:

0,2,0,4,0,0,7,8

故问题转化为求最长上升子序列。

Code

#include <bits/stdc++.h>

typedef long long LL;


int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n;
	std::cin >> n;
	std::vector<int> p;
	for (int i = 1; i <= n; i++) {
		int x;
		std::cin >> x;
		if (p.size() == 0 || x > p.back()) p.push_back(x);
		else *std::lower_bound(p.begin(), p.end(), x) = x;
	}

	std::cout << p.size() << '\n';


	return 0;
}

G

很裸的 DP,令 dpi,j 表示 第 i 个月在 j 号城市的最大价值。

枚举边集转移即可,复杂度 O(k(m+n))

Code

#include <bits/stdc++.h>

typedef long long LL;

const LL INF = 1e18, N = 5010;
LL dp[N], ndp[N];
int a[N];
std::vector<int> g[N];

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n, m, k;
	std::cin >> n >> m >> k;
	for (int i = 1; i <= n; i++) {
		std::cin >> a[i];
	}
	while (m--) {
		int u, v;
		std::cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}

	for (int i = 1; i <= n; i++) {
		dp[i] = -INF;
	}
	dp[1] = a[1];
	for (int i = 2; i <= k; i++) {
		for (int j = 1; j <= n; j++) {
			ndp[j] = -INF;
		}
		for (int j = 1; j <= n; j++) {
			for (auto c : g[j]) {
				if (c != j) ndp[c] = std::max(ndp[c], dp[j] + a[c]);
			}
		}
		for (int j = 1; j <= n; j++) {
			dp[j] = ndp[j];
		}
	}

	if (dp[1] <= 0) {
		std::cout << -1 << '\n';
	} else {
		std::cout << dp[1] << '\n';
	}

	return 0;
}

H

枚举 i,问题转化为在 1j<i 中的所有物品,在花费不超过 mai 的情况下,最多能买多少个物品。

直接维护一个值域线段树(离散化,或者动态开点即可),然后在线段树上面二分即可。

Code

#include <bits/stdc++.h>

typedef long long LL;

const int N = 1e5 + 10, INF = 1e9;
int a[N], root[N], idx;
struct node {
	int l, r;
	int cnt;
	LL sum;
} tr[40 * N];

void insert(int &u, int v, int l, int r, int x) {
	u = ++idx;
	tr[u] = tr[v];
	tr[u].cnt++, tr[u].sum += x;
	if (l == r) return;
	int mid = (l + r) >> 1;
	if (x <= mid) insert(tr[u].l, tr[v].l, l, mid, x);
	else insert(tr[u].r, tr[v].r, mid + 1, r, x);
}

int query(int u, int l, int r, int k) {
	if (l == r) {
        if (l == 0) return tr[u].cnt;
        return std::min(k / l, tr[u].cnt);
	}
	int mid = (l + r) >> 1;
	if (tr[tr[u].l].sum <= k) {
		return tr[tr[u].l].cnt + query(tr[u].r, mid + 1, r, k - tr[tr[u].l].sum);
	}
	return query(tr[u].l, l, mid, k);
}

void solve() {
	int n, m;
	std::cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		std::cin >> a[i];
		root[i] = 0;
	}
	idx = 0;
	for (int i = 1; i <= n; i++) {
		int cost = m - a[i];	
		std::cout << i - 1 - query(root[i - 1], 0, INF, cost) << " \n"[i == n];
		insert(root[i], root[i - 1], 0, INF, a[i]);
	}
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int t;
	std::cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

I

式子可以转化为:

(a12+a22+...+an2)2×x×(a1+a2+...+an)n×x2

提前算好 a12+a22+...+an2a1+a2+...+an 即可做到 O(1) 查询了。

Code

#include <bits/stdc++.h>

typedef long long LL;


int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n, q;
	std::cin >> n >> q;

	//a ^ 2 - 2ax + x ^ 2
	LL s1 = 0, s2 = 0;
	for (int i = 1; i <= n; i++) {
		int x;
		std::cin >> x;
		s1 += 1LL * x * x;
		s2 += 2 * x;
	}

	while (q--) {
		int x;
		std::cin >> x;
		std::cout << s1 - x * s2 + 1LL * n * x * x << '\n';
	}
	
	return 0;
}

J

看到 t 很大,且式子是一个递推的东西,很容易想到矩阵快速幂优化,我们考虑如下转化,由于要取余,我们不妨做如下映射:

我们把 n 看成 0,那么 (x+dx1)%n+1 可以映射为 (x+dx)%n

那么我们的坐标变换就变成直接模 n 意义下的东西了,可以直接考虑递推,推一下转移矩阵,很容易得到:

[x,y,dx,dy,t,1]×[2,1,1,1,0,01,2,1,1,0,01,0,1,0,0,00,1,0,1,0,01,1,1,1,1,00,0,0,0,1,1]=[x,y,dx,dy,t,1]

其中 t 表示当且时刻,一开始 t=0

时间复杂度为 (63logt)

最后要映射回去,即若 x=0,那么实际上 x=n

Code

#include <bits/stdc++.h>

typedef long long LL;

LL n, sx, sy, dx, dy, t;

LL f[6][6], g[6][6] = {
	{2, 1, 1, 1, 0, 0},
	{1, 2, 1, 1, 0, 0},
	{1, 0, 1, 0, 0, 0},
	{0, 1, 0, 1, 0, 0},
	{1, 1, 1, 1, 1, 0},
	{0, 0, 0, 0, 1, 1}
};

void mul(LL a[][6], LL b[][6], LL c[][6]) {
	static LL tmp[6][6];
	std::memset(tmp, 0, sizeof tmp);
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			for (int k = 0; k < 6; k++) {
				tmp[i][j] = (tmp[i][j] + a[i][k] * b[k][j] % n) % n;
			}
		}
	}
	std::memcpy(c, tmp, sizeof tmp);
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	std::cin >> n >> sx >> sy >> dx >> dy >> t;
	f[0][0] = sx % n, f[0][1] = sy % n, f[0][2] = dx % n, f[0][3] = dy % n, f[0][4] = 0, f[0][5] = 1;

	while (t) {
		if (t & 1) mul(f, g, f);
		mul(g, g, g);
		t >>= 1;
	}
	
    std::cout << (f[0][0] == 0 ? n : f[0][0]) << " " << (f[0][1] == 0 ? n : f[0][1]) << '\n';

	return 0;
}

``
posted @   jackle  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示