当前位置: 首页 > article >正文

ABC395题解

A

算法标签: 模拟

问题陈述

给你一个正整数 N N N 和一个长度为 N N N 的正整数序列 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \dots, A_N) A=(A1,A2,,AN)

请判断 A A A 是否是严格递增的,也就是说, A i < A i + 1 A_i < A_{i+1} Ai<Ai+1 是否对每一个满足 1 ≤ i < N 1 \leq i < N 1i<N 的整数 i i i 都成立

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int n;
	cin >> n;
	int last = 0;

	for (int i = 0; i < n; ++i) {
		int val;
		cin >> val;
		if (val <= last) {
			cout << "No" << "\n";
			return 0;
		}
		last = val;
	}

	cout << "Yes" << "\n";
	return 0;
}

B

算法标签: 模拟

问题陈述

概述:创建一个 N × N N \times N N×N 模式,如下所示。

给你一个正整数 N N N

考虑一个 N × N N \times N N×N 网格。让 ( i , j ) (i,j) (i,j) 表示从上往下第 i i i 行,从左往右第 j j j 列的单元格。最初,没有单元格被着色。

然后,依次对 i = 1 , 2 , … , N i = 1,2,\dots,N i=1,2,,N 执行以下操作:

  • j = N + 1 − i j = N + 1 - i j=N+1i
  • 如果 i ≤ j i \leq j ij,则在左上角单元格为 ( i , i ) (i,i) (i,i)、右下角单元格为 ( j , j ) (j,j) (j,j) 的矩形区域填充黑色(如果 i i i 为奇数),如果 i i i 为偶数,则填充白色。如果某些单元格已经着色,则覆盖它们的颜色。
  • 如果 i > j i > j i>j,则不做任何操作。

经过所有这些操作后,可以证明没有未着色的单元格。确定每个单元格的最终颜色。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

const int N = 55;

int n;
char m[N][N];

void draw(int i, int j, bool tag) {
	char c = tag ? '#' : '.';
	for (int k = i; k <= j; ++k) {
		for (int l = i; l <= j; ++l) {
			m[k][l] = c;
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> n;
	for (int i = 1; i <= (n + 1) / 2; ++i) {
		int j = n + 1 - i;
		if (i % 2) draw(i, j, true);
		else draw(i, j, false);
	}

	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			cout << m[i][j];
		}
		cout << "\n";
	}

	return 0;
}

C

算法标签: 哈希表, 滑动窗口

问题陈述

给你一个正整数 N N N 和一个长度为 N N N 的整数序列 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \dots, A_N) A=(A1,A2,,AN)

请判断 A A A 是否存在一个非空(连续)子数组,它有一个重复值,多次出现在 A A A 中。如果存在这样的子数组,求最短的子数组的长度。

思路

如果存在当前元素 w [ r ] w[r] w[r], 更新答案并且收缩左侧窗口, 直到不存在当前元素 w [ r ] w[r] w[r], 也就是维护窗口中不存在重复元素

#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_set>

using namespace std;

const int N = 2e5 + 10, INF = 0x3f3f3f3f;

int n, w[N];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	unordered_set<int> s;
	cin >> n;
	for (int i = 0; i < n; ++i) cin >> w[i];

	int res = INF;
	int l = 0;

	for (int r = 0; r < n; ++r) {
		while (s.count(w[r])) {
			res = min(res, r - l + 1);
			s.erase(w[l]);
			l++;
		}
		s.insert(w[r]);
	}

	if (res == INF) res = -1;
	cout << res << "\n";
	return 0;
}

D

算法标签: 模拟

问题陈述

N N N 只标有 1 , 2 , … , N 1, 2, \ldots, N 1,2,,N 的鸽子和 N N N 个标有 1 , 2 , … , N 1, 2, \ldots, N 1,2,,N 的鸽子窝。

最初,鸽子 i i i ( 1 ≤ i ≤ N ) (1 \leq i \leq N) (1iN) 在巢 i i i 中。

您将对这些鸽子进行 Q Q Q 次操作。

操作有三种:

  1. 类型 1 1 1:给你整数 a a a b b b ( 1 ≤ a ≤ N , 1 ≤ b ≤ N ) (1 \leq a \leq N, 1 \leq b \leq N) (1aN,1bN)。将鸽子 a a a 从当前巢中取出,移到巢 b b b 中。
  2. 类型 2 2 2:给你整数 a a a b b b ( 1 ≤ a < b ≤ N ) (1 \leq a < b \leq N) (1a<bN)。将巢 a a a 中的所有鸽子移动到巢 b b b,并将巢 b b b 中的所有鸽子移动到巢 a a a。这两次移动是同时进行的。
  3. 类型 3 3 3:给你一个整数 a a a ( 1 ≤ a ≤ N ) (1 \leq a \leq N) (1aN)。鸽子 a a a 报告它目前所在巢的标签。

按照给出的操作顺序打印每个 类型 3 3 3 操作的结果。

输入格式

  • 第一行包含两个整数 N N N Q Q Q,分别表示鸽子和操作的数量。
  • 接下来的 Q Q Q 行,每行描述一个操作:
    • 对于类型 1 1 1 操作,格式为 1 a b
    • 对于类型 2 2 2 操作,格式为 2 a b
    • 对于类型 3 3 3 操作,格式为 3 a

输出格式

对于每个类型 3 3 3 操作,输出鸽子 a a a 当前所在的巢的标签。

思路

实现分析数据范围, 鸽子的数量 1 0 6 10 ^ 6 106, 操作数量 1 0 5 10 ^ 5 105, 实现 O ( n 2 ) O(n ^ 2) O(n2)以下时间复杂度
考虑为每一个位置确定一个门牌号, 交换两个鸽子笼是最消耗时间的, 但是交换门牌号很简单
在这里插入图片描述
上述是鸽子笼初始状态

在这里插入图片描述
上述是交换两个鸽子笼后, 也就是交换两个门牌号后的情况
设数组 p [ i ] p[i] p[i], 代表鸽子 i i i所属鸽子笼编号, p o s [ i ] pos[i] pos[i]代表门牌号 i i i当前是哪个鸽子笼
考虑操作 1 1 1, 鸽子 a a a从当前鸽子笼中取出移动到鸽子笼 b b b中, 因为交换的是门牌号, 因此需要知道每个笼子所属的门牌号是多少, 记为 n u m [ i ] num[i] num[i]

第一个操作

  • 首先找到巢 b b b所在的门牌号
  • 然后将鸽子 a a a放入该鸽子笼中

第二个操作

  • 找到两个笼子所属的门牌号
  • 交换两个门牌号

第三个操作

  • p [ a ] p[a] p[a]代表鸽子 a a a所属的门牌号
  • 输出当前门牌号对应的笼子编号

时间复杂度 O ( M ) O(M) O(M)

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e6 + 10;

int n, m;
// 鸽子i所属门牌号, 门牌号i的笼子编号, 笼子i的门牌号
int p[N], pos[N], num[N];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> n >> m;
	for (int i = 1; i <= n; ++i) p[i] = pos[i] = num[i] = i;

	while (m--) {
		int op;
		cin >> op;
		if (op == 1) {
			int a, b;
			cin >> a >> b;
			int num_b = num[b];
			p[a] = num_b;
		}
		else if (op == 2) {
			int a, b;
			cin >> a >> b;
			int num_a = num[a];
			int num_b = num[b];
			swap(num[a], num[b]);
			swap(pos[num_a], pos[num_b]);
		}
		else {
			int a;
			cin >> a;
			int num_a = p[a];
			cout << pos[num_a] << "\n";
		}
	}

	return 0;
}

E

算法标签: 最短路, 分层图

问题陈述

给你一个有向图,图中有 N N N 个顶点和 M M M 条边。 i i i -th 边 ( 1 ≤ i ≤ M ) (1 \leq i \leq M) (1iM) 是一条从顶点 u i u_i ui 到顶点 v i v_i vi 的有向边。

最初,您位于顶点 1 1 1 。您要重复以下操作直到到达顶点 N N N

  • 执行以下两个操作之一:
    • 从当前顶点沿一条有向边移动。这样做的代价是 1 1 1 。更精确地说,如果你在顶点 v v v ,选择一个顶点 u u u ,使得有一条从 v v v u u u 的有向边,然后移动到顶点 u u u
    • 逆转所有边的方向这样做的代价是 X X X 。更确切地说,如果且仅如果在此操作之前有一条从 v v v u u u 的有向边,那么在此操作之后有一条从 u u u v v v 的有向边。

可以保证,对于给定的图,重复这些操作可以从顶点 1 1 1 到达顶点 N N N

求到达顶点 N N N 所需的最小总成本。

输入格式

  • 第一行包含三个整数 N N N, M M M, X X X,分别表示顶点数、边数和逆转边的代价。
  • 接下来的 M M M 行,每行包含两个整数 u i u_i ui, v i v_i vi,表示一条从 u i u_i ui v i v_i vi 的有向边。

输出格式

输出一个整数,表示从顶点 1 1 1 到达顶点 N N N 所需的最小总成本。

思路

在这里插入图片描述
下面一层图就是反转图, 从上一层到下一层的花费是 T T T

注意目标节点之间转移花费是 0 0 0

在这里插入图片描述
因为两次反转之后回到原图, 因此只需要建立两层即可, 时间复杂度 O ( n log ⁡ m ) O(n \log m) O(nlogm)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

typedef long long LL;
typedef pair<LL, int> PII;
const int N = (2e5 + 10) * 2, M = (2e5 + 10) * 4;

int n, m, cost;
int head[N], edge_end[M], next_edge[M], w[M], edge_index;
LL d[N];
bool vis[N];

void add(int u, int v, int val) {
	edge_end[edge_index] = v, next_edge[edge_index] = head[u], w[edge_index] = val, head[u] = edge_index++;
}

void dijkstra() {
	priority_queue<PII, vector<PII>, greater<PII>> q;
	memset(d, 0x3f, sizeof d);
	d[1] = 0;
	q.push({0, 1});

	while (!q.empty()) {
		auto [dis, u] = q.top();
		q.pop();

		if (vis[u]) continue;
		vis[u] = true;

		for (int i = head[u]; ~i; i = next_edge[i]) {
			int ver = edge_end[i];
			if (d[u] + w[i] < d[ver]) {
				d[ver] = d[u] + w[i];
				q.push({d[ver], ver});
			}
		}
	}
}


int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	memset(head, -1, sizeof head);

	cin >> n >> m >> cost;
	for (int i = 0; i < m; ++i) {
		int u, v;
		cin >> u >> v;
		add(u, v, 1);
		add(v + n, u + n, 1);
	}
	
	for (int i = 1; i <= n; ++i) {
		add(i, i + n, cost);
		add(i + n, i, cost);
	}

	dijkstra();

	LL res = 9e18;
	res = min({res, d[n], d[n + n]});

	cout << res << "\n";
	return 0;
}

F

算法标签: 贪心, 整数二分

问题陈述

高桥有 2 N 2N 2N 颗牙齿:上牙有 N N N 颗,下牙有 N N N 颗。
左起第 i i i 颗上牙( 1 ≤ i ≤ N 1 \leq i \leq N 1iN)的长度是 U i U_i Ui,左起第 i i i 颗下牙( 1 ≤ i ≤ N 1 \leq i \leq N 1iN)的长度是 D i D_i Di

如果同时满足以下两个条件,那么他的牙齿就可以说是 “合得很好”:

  1. 存在一个整数 H H H,使得对于每个整数 i i i 1 ≤ i ≤ N 1 \leq i \leq N 1iN), U i + D i = H U_i + D_i = H Ui+Di=H
  2. 对于每个整数 i i i 1 ≤ i < N 1 \leq i < N 1i<N), ∣ U i − U i + 1 ∣ ≤ X \lvert U_i - U_{i+1} \rvert \leq X UiUi+1X

他可以多次进行下面的运算:

  • 支付 1 1 1 日元使用磨齿机,选择一个长度为正数的牙齿,并将其长度减少 1 1 1

不得使用其他方法改变牙齿的长度。
求为了使牙齿整齐排列,他至少需要支付的总金额。

思路

如果 H H H确定, 那么花费确定

c o s t = ∑ i = 1 N ( U i + D i )   − N × H cost = \sum _{i = 1} ^ {N} (U_i + D_i) \, - N \times H cost=i=1N(Ui+Di)N×H

因此花费最小化, 也就是 H H H最大化, 问题转化为如何找到最大的 H H H使得花费最小, 具有单调性

假设存在一个最大的 H H H, 那么考虑 H − 1 H - 1 H1, 将 H H H方案变为 H − 1 H - 1 H1的方案, 如何变化?

假设 H H H下的方案为 U i ′ U'_i Ui D i ′ D'_i Di, 需要将 U i ′ − 1 U'_i - 1 Ui1或者 D i ′ − 1 D'_i - 1 Di1,
假设只变化 U i ′ U'_i Ui, 如果发现 U i ′ = 0 U'_i = 0 Ui=0, 将 D i ′ − 1 D'_i - 1 Di1, 也是符合情况的, 因此 H H H是合法的, H − 1 H - 1 H1必定是合法的,
因此 H H H具有单调性, 一定存在一个最大的 H H H, 使得花费最小

二分 H H H判断是否能达到题目的两个条件

  1. 是否存在序列 A i A_i Ai B i B_i Bi使得 A i + B i = H A_i + B_i = H Ai+Bi=H
  2. 对于每一个 A i A_i Ai, 是否有 ∣ A i − 1 − A i ∣ ≤ X |A_{i - 1} - A_i| \le X Ai1AiX

设变化后的上牙长度为 U i ′ U'_i Ui, 那么由于只能变小, 因此 U i ′ ≤ U i U'_i \le U_i UiUi, H − U i ′ = D i ′ ≤ D i H - U'_i = D'_i \le D_i HUi=DiDi

因此有 max ⁡ { 0 , H − D i } ≤ U i ′ ≤ U i \max\left \{0, H - D_i \right \} \le U'_i \le U_i max{0,HDi}UiUi, 也就是说, 对于每一个 i i i, U i ′ U'_i Ui最终的取值是都有个范围的, 设左边界为 l [ i ] l[i] l[i], 右边界为 r [ i ] r[i] r[i]

因此问题转化为, 对于每个 l [ i ] ≤ U i ≤ r [ i ] l[i] \le U_i \le r[i] l[i]Uir[i], 是否存在一组数使得 ∣ U i − U i − 1 ∣ ≤ X |U_i - U{i - 1}| \le X UiUi1X
, 也就是满足 U i − 1 − X ≤ U i ≤ U i − 1 + X U_{i - 1} - X \le U_i \le U_{i - 1} + X Ui1XUiUi1+X, 同时满足上述两个不等式, 如果发现不满足说明无解

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;
const int N = 2e5 + 10;

int n, m;
LL u[N], d[N];

// 传入H
bool check(LL val) {

	LL l = max(0ll, val - d[1]);
	LL r = u[1];

	for (int i = 2; i <= n; ++i) {
		l = max({l - m, 0ll, val - d[i]});
		r = min(r + m, u[i]);
		if (l > r) return false;
	}

	return true;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	LL l = 0, r = 2e9;
	LL sum = 0;
	cin >> n >> m;

	for (int i = 1; i <= n; ++i) {
		cin >> u[i] >> d[i];
		r = min(r, u[i] + d[i]);
		sum += u[i] + d[i];
	}

	LL res = 0;
	while (l < r) {
		LL mid = l + r + 1 >> 1;
		if (check(mid)) l = mid;
		else r = mid - 1;
	}

	res = l;
	cout << sum - n * res << "\n";
	return 0;
}

G

算法标签: S t e i n e r − T r e e Steiner-Tree SteinerTree, 斯坦纳树, 状态压缩 d p dp dp

问题陈述

给你一个正整数 N , K N, K N,K 和一个 N × N N \times N N×N 矩阵 C = ( C i , j ) C=(C_{i,j}) C=(Ci,j),其中 ( 1 ≤ i , j ≤ N ) (1 \leq i, j \leq N) (1i,jN)。保证 C i , i = 0 C_{i,i} = 0 Ci,i=0 C i , j = C j , i C_{i,j} = C_{j,i} Ci,j=Cj,i

有一个标有 1 , 2 , … , N 1, 2, \dots, N 1,2,,N 的加权完全图,其中有 N N N 个顶点。对于每一对有 i < j i < j i<j 的顶点 i i i j j j,连接顶点 i i i j j j 的边的权重为 C i , j C_{i,j} Ci,j

您有 Q Q Q 个查询。在第 i i i 个查询中,我们给出了一对不同的整数 s i s_i si t i t_i ti(这两个整数都在 K + 1 K+1 K+1 N N N 之间,包括首尾两个整数),你必须解决下面的问题。

一个好的图是一个连通的图,包含从上图中删除任意数量的边和顶点(可能为零)后得到的所有顶点 1 , 2 , … , K , s i , t i 1, 2, \dots, K, s_i, t_i 1,2,,K,si,ti

好图的成本是其边的权重之和。

求一个好图的最小可能成本。

思路

最小斯坦纳树
P6192 【模板】最小斯坦纳树

最小斯坦纳树: 给出图中任意点集 V V V, 要求将 V V V连通需要选择那些边, 并且使得这些边的权值之和最小

只能状态压缩 d p dp dp解决该问题, 假设一共 N N N个点, 希望将 [ 1 , k ] [1, k] [1,k]之间的点连通, 求最小花费
设计状态 f [ I ] [ s ] f[I][s] f[I][s]表示以 i i i为根, 至少将点集 s s s连通的最小代价, s s s是个二进制数

i i i可以不属于 s s s, s s s [ 1 , k ] [1, k] [1,k]的子集, a n s = min ⁡ { f [ u ] [ 2 k − 1 ] } ans = \min \left \{ f[u][2 ^ k - 1] \right \} ans=min{f[u][2k1]}

如何进行状态转移?

  • 分裂, 将集合分为两部分, f [ i ] [ s ] = min ⁡ { f [ i ] [ T ] + f [ i ] [ S ∣ T ] } f[i][s] = \min \left \{ f[i][T] + f[i][S | T] \right \} f[i][s]=min{f[i][T]+f[i][ST]}, 限制 T T T, 转移是不带环的
  • 换根, f [ u ] [ s ] = min ⁡ ( f [ u ] [ s ] , f [ i ] [ s ] + w [ i ] [ u ] ) f[u][s] = \min (f[u][s], f[i][s] + w[i][u]) f[u][s]=min(f[u][s],f[i][s]+w[i][u]), 转移是带环的, 无法直接计算

但是, 第二个转移方程非常类似于最短路算法, f [ u ] = m i n ( f [ u ] , f [ i ] + w ) f[u] = min(f[u], f[i] + w) f[u]=min(f[u],f[i]+w), 使用 D i j k s t r a Dijkstra Dijkstra算法优化

因此首先用第一个转移方式计算子集 s s s的贡献, 然后再用第二个转移方式计算 s s s之间的贡献
时间复杂度 O ( n 3 K + n 2 2 K ) O(n3 ^ K + n ^ 22^K) O(n3K+n22K)

对于本题来说, 不仅仅在求 [ 1 , k ] [1, k] [1,k]的斯坦纳树, 还要加上两个点 s s s t t t, 不能直接进行暴力状态压缩 d p dp dp, 在询问基础上进行预处理



http://www.kler.cn/a/593769.html

相关文章:

  • 浅谈canal实例 在docker里面安装canal镜像 Canal监听MySQL数据库变更并同步更新Redis和Elasticsearch 示例
  • 共筑智慧城市新生态!YashanDB与荣科科技完成兼容互认证
  • Hive高频SQL及典型应用场景总结
  • 【Pandas】pandas Series plot
  • STC89C52单片机学习——第28节: [12-2] AT24C02数据存储秒表(定时器扫描按键数码管)
  • 外星人入侵-Python-三
  • C++学习之nginx+fastDFS
  • 深入解析 Redis 原理:架构、数据结构与高效存储
  • 基于开源模型的微调训练及瘦身打造随身扫描仪方案__用AI把手机变成文字识别小能手
  • 【Vue3】01-vue3的基础 + ref reactive
  • Pygame实现记忆拼图游戏14
  • 实时数仓和离线数仓
  • subprocess执行系统命令简明用法
  • 「低延迟+快速集成:Amazon IVS如何重塑实时互动视频体验?」
  • Linux与HTTP中的Cookie和Session
  • 头歌实训--数据预处理Pandas--共三关
  • 黄金屋 #2 我应该将产品开源吗?
  • 雅可比行列式
  • fontTools工具的使用介绍
  • [DeepRetrieval] 用DeepSeek-R1-Zero的思路教会模型怎么用搜索引擎找文本