ABC397题解

A

算法标签: 模拟

#include <iostream>
#include <algorithm>

using namespace std;

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

	double val;
	cin >> val;

	if (val >= 38.0) cout << 1 << "\n";
	else if (val >= 37.5) cout << 2 << "\n";
	else cout << 3 << "\n";
	
	return 0;
}

B

算法标签: 贪心

#include <iostream>
#include <string>
using namespace std;

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

	string s;
	cin >> s;
	int n = s.size();

	int res = 0;
	int i = 0;
	
	while (i < n) {
		if ((i + res) % 2 == 0) {
			if (s[i] == 'o') res++;
			else i++;
		}
		else {
			if (s[i] == 'i') res++;
			else i++;
		}
	}
	
	if ((n + res) % 2 != 0) res++;

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

C

算法标签: 哈希表, 前缀和

#include <iostream>
#include <algorithm>
#include <unordered_map>

using namespace std;

const int N = 3e5 + 10;

int n;
int w[N];
unordered_map<int, int> m1, m2;

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

	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> w[i];
		if (m2.count(w[i])) m2[w[i]]++;
		else m2.insert({w[i], 1});
	}

	int res = 1;
	for (int i = 1; i <= n; ++i) {
		if (m1.count(w[i])) m1[w[i]]++;
		else m1.insert({w[i], 1});
		m2[w[i]]--;
		if (m2[w[i]] == 0) m2.erase(w[i]);
		res = max(res, (int) m1.size() + (int) m2.size());
	}

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

前缀后缀统计不同数字出现的次数

左右开两个, f [ i ] f[i] f[i]表示从开始位置到 i i i的不同数字的个数, g [ i ] g[i] g[i], 代表从后面开始向前的不同数字的个数

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

using namespace std;

const int N = 3e5 + 10;

int n;
int w[N];
int pre[N], suff[N];
bool vis[N];

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

	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> w[i];

	for (int i = 1; i <= n; ++i) {
		pre[i] = pre[i - 1];
		if (!vis[w[i]]) pre[i]++;
		vis[w[i]] = true;
	}

	memset(vis, false, sizeof vis);

	for (int i = n; i; --i) {
		suff[i] = suff[i + 1];
		if (!vis[w[i]]) suff[i]++;
		vis[w[i]] = true;
	}

	int res = 0;
	// 枚举分割点
	for (int i = 1; i <= n; ++i) {
		int val = pre[i - 1] + suff[i];
		res = max(res, val);
	}

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

D

算法标签: 整数二分, 数学知识

根据立方差公式, d = x − y d = x - y d=xy, d d d满足的性质是 d ∣ N d | N dN
带入原式 ( y + d ) 3 − y 3 = N (y + d) ^ 3 - y ^ 3 = N (y+d)3y3=N
化简得到 3 y 2 d + 3 y d 2 + d 3 = N 3y^2d + 3yd^2 + d^3 = N 3y2d+3yd2+d3=N
d 3 ≤ N d ^ 3 \le N d3N, 得到 d d d的范围 1 0 6 10 ^ 6 106, 求根公式直接求解一元二次方程, 时间复杂度 N 3 \sqrt[3]{N} 3N

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

using namespace std;

typedef long long LL;
const int N = 1e6 + 10;

int solve(LL a, LL b, LL c) {
	// ay^2 + by + c = 0
	LL l = 0, r = 1e9;
	while (l < r) {
		int mid = l + r + 1 >> 1;
		if (a * mid * mid + b * mid + c > 0) r = mid - 1;
		else l = mid;
	}

	if (a * l * l + b * l + c == 0) return l;
	return -1;
}

int main() {
	LL n;
	cin >> n;

	for (LL d = 1; d * d * d <= n; ++d) {
		if (n % d) continue;
		LL l = solve(3, 3 * d, d * d - n / d);
		if (l > 0) {
			cout << l + d << " " << l << "\n";
			return 0;
		}
	}

	cout << -1 << "\n";
	return 0;
}

E

算法标签: 树形 d p dp dp

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

using namespace std;

const int N = 2e5 + 10, M = N << 1;

int n, k;
int head[N], edge_end[M], next_edge[M], edge_index;
int sz[N];
bool tag = true;

void add(int ver1, int ver2) {
	edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], head[ver1] = edge_index++;
}

void No() {
	cout << "No" << "\n";
	exit(0);
}

void dfs(int u, int fa) {
	sz[u] = 1;
	int son = 0;
	for (int i = head[u]; ~i; i = next_edge[i]) {
		int ver = edge_end[i];
		if (ver == fa) continue;

		dfs(ver, u);

		if (sz[ver]) {
			son++;
			sz[u] += sz[ver];
		}
	}

	if (sz[u] < k) {
		if (u == 1 || son > 1) No();
	}
	else if (sz[u] == k) {
		if (son > 2) No();
		// 将当前位置子树删除
		sz[u] = 0;
	}
	else No();
}

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

	memset(head, -1, sizeof head);
	cin >> n >> k;
	for (int i = 1; i < n * k; ++i) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}

	dfs(1, -1);
	cout << "Yes" << "\n";

	return 0;
}

F

算法标签: 前缀和, 线段树

切两刀的最优方案, C C C题是切一刀的最优方案
如果仍然像 C C C一样枚举分界点, 那么时间复杂度 O ( n 2 ) O(n ^ 2) O(n2), 会超时, 需要将算法优化到 O ( n log ⁡ n ) O(n\log {n}) O(nlogn)以下

假设固定 j j j, 枚举最优的 i i i, 最大价值如下

s u f j + 1 + max ⁡ 1 ≤ i < j { p r e i + ∑ k = i + 1 j c n t k } suf_{j + 1} + \max_{1\le i < j} \left \{pre_i + \sum_{k=i + 1} ^ {j} cnt_k \right \} sufj+1+1i<jmax{prei+k=i+1jcntk}

因为枚举 j j j, 因此第一项是常量, 第二项必须在 log ⁡ n \log{n} logn时间复杂度之内计算出

如何快速的计算出

p r e i + ∑ k = i + 1 j c n t k pre_i + \sum_{k = i + 1} ^ {j} cnt_k prei+k=i+1jcntk

第一项 p r e 1 + [ 2 , j ] pre_1 + [2, j] pre1+[2,j], 第二项 p r e 2 + [ 3 , j ] pre_2 + [3, j] pre2+[3,j], 第三项 p r e 3 + [ 4 , j ] pre_3 + [4, j] pre3+[4,j]

假设对于每个 i i i, 都计算出这样一个数值, 对于 j j j的数组如上, 如果 j j j变为 j + 1 j + 1 j+1上述数组会产生什么样的变化?
区间修改, 查询区间最大值, 线段树维护

考虑 j j j向后移动的过程, 发生两个变化, 首先多出一项

p r e j + ∑ k = j + 1 j + 1 c n t k pre_j + \sum_{k = j + 1}^{j + 1} cnt_k prej+k=j+1j+1cntk

形式化的说
在这里插入图片描述
井号表示区间个数, 对于每一项添加一个 a j + 1 a_{j + 1} aj+1, 当前数有没有变化取决于 a j + 1 a_{j + 1} aj+1在之前是否出现过
形式化的说, 第 i i i项有变化, 当且仅当 [ i + 1 , j ] [i + 1, j] [i+1,j]之间未出现 a j + 1 a_{j + 1} aj+1
如果单当前区间未出现 a j + 1 a_{j + 1} aj+1, 那么范围比当前区间小的区间也一定未出现 a j + 1 a_{j + 1} aj+1, 具有一个二分的性质

序列需要支持的操作

  • 维护全局最大值
  • 区间加 1 1 1
  • 末尾加一项, 等价于单点操作

线段树维护该数组, 时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

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

using namespace std;

const int N = 3e5 + 10;

int n;
int w[N], pre[N], suff[N], vis[N], last[N];
int tr[N << 2], tag[N << 2];

void add(int u, int l, int r, int ql, int qr, int val) {
	if (l >= ql && r <= qr) {
		tr[u] += val;
		tag[u] += val;
		return;
	}

	tag[u << 1] += tag[u], tr[u << 1] += tag[u];
	tag[u << 1 | 1] += tag[u], tr[u << 1 | 1] += tag[u];
	tag[u] = 0;

	int mid = l + r >> 1;
	if (ql <= mid) add(u << 1, l, mid, ql, qr, val);
	if (qr > mid) add(u << 1 | 1, mid + 1, r, ql, qr, val);

	tr[u] = max(tr[u << 1], tr[u << 1 | 1]);
}

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

	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> w[i];

	for (int i = 1; i <= n; ++i) {
		pre[i] = pre[i - 1];
		last[i] = vis[w[i]];
		if (!vis[w[i]]) pre[i]++;
		vis[w[i]] = i;
	}

	memset(vis, 0, sizeof vis);

	for (int i = n; i >= 0; --i) {
		suff[i] = suff[i + 1];
		if (!vis[w[i]]) suff[i]++;
		vis[w[i]] = true;
	}

	int res = 0;
	for (int j = 1; j < n; ++j) {
		res = max(res, suff[j + 1] + tr[1]);
		add(1, 1, n, j, j, pre[j]);
		// 后面较小的区间未出现, 需要变化
		add(1, 1, n, last[j + 1], j, 1);
	}

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

G

算法标签: 二分, 网络流, 分层图

最开始所有边权是 0 0 0, 选出 K K K条边, 改成边权 1 1 1, 求最短路的最大值

因为求最大值最小, 因此考虑二分答案 d d d, 如果要求答案 ≥ d \ge d d, 至少需要修改几条边
首先讨论 d = 1 d = 1 d=1的情况
在这里插入图片描述
d = 1 d = 1 d=1也就是最短路至少是 1 1 1, 换言之就是至少需要修改几条边使得原图不连通, 修改一条边的边权可以看作将一条边割开, d = 1 d = 1 d=1情况下, 求最小割

最大流最小割定理证明了最大流等于最小割, 因此就是求最大流

然后讨论 d = 2 d = 2 d=2的情况
割掉任何一条边代价都是 1 1 1, d = 1 d = 1 d=1判定的是是否存在最短路 d i s t = 0 dist = 0 dist=0

在这里插入图片描述
如果能从起点走到 2 2 2号点, d i s t 2 = 0 dist_2 = 0 dist2=0, 目标是 4 4 4号点的最短路不是 0 0 0, d i s t 4 ≠ 0 dist_4 \ne 0 dist4=0

在这里插入图片描述
建立分层图
上标代表从起点 s s s v 0 v ^ 0 v0, 存在长度为 0 0 0的路径, 起点是 1 0 1 ^ 0 10
目标是从起点出发不能走到终点

在这里插入图片描述
至少割四条边, 使得 d = 2 d = 2 d=2, 时间复杂度 O ( N 5 M log ⁡ N ) O(N ^ 5M\log N) O(N5MlogN)

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

using namespace std;

const int N = 10010, M = 200010, INF = 0x3f3f3f3f;

int n, m, k, s_node, e_node;
int head[N], edge_end[M], next_edge[M], w[M], edge_index;
int q[N], layer[N], curr[N];
int s_nodes[N], e_nodes[N];

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

bool bfs() {
	memset(layer, -1, sizeof layer);

	int h = 0, t = -1;
	q[++t] = s_node;
	layer[s_node] = 0;
	curr[s_node] = head[s_node];

	while (h <= t) {
		int u = q[h++];

		for (int i = head[u]; ~i; i = next_edge[i]) {
			int ver = edge_end[i];

			if (layer[ver] == -1 && w[i]) {
				layer[ver] = layer[u] + 1;
				curr[ver] = head[ver];
				if (ver == e_node) return true;
				q[++t] = ver;
			}
		}
	}

	return false;
}

int dfs(int u, int limit) {
	if (u == e_node) return limit;

	// 从当前点向后流的流量
	int flow = 0;
	for (int i = curr[u]; ~i && flow < limit; i = next_edge[i]) {
		int ver = edge_end[i];

		// i前面的边都用完了, 当前弧更新为i
		curr[u] = i;
		if (layer[ver] == layer[u] + 1 && w[i]) {
			int val = dfs(ver, min(w[i], limit - flow));
			if (!val) layer[ver] = -1;

			w[i] -= val;
			w[i ^ 1] += val;
			flow += val;
		}
	}
	return flow;
}

int dinic() {
	int res = 0, flow;
	while (bfs()) {
		// 搜索增广路径并且累计全部的流量
		while ((flow = dfs(s_node, INF))) {
			res += flow;
		}
	}

	return res;
}

bool check(int val) {
	memset(head, -1, sizeof head);
	edge_index = 0;

	// 同一层的边
	for (int i = 1; i <= val; ++i) {
		for (int j = 1; j <= m; ++j) {
			int u = s_nodes[j];
			int v = e_nodes[j];
			add((i - 1) * n + u, (i - 1) * n + v, 1);
			add((i - 1) * n + v, (i - 1) * n + u, 0);
		}
	}

	// 层与层之间的边
	for (int i = 1; i < val; ++i) {
		for (int j = 1; j <= m; ++j) {
			int u = s_nodes[j];
			int v = e_nodes[j];
			add((i - 1) * n + u, i * n + v, 1e8);
			add(i * n + v, (i - 1) * n + u, 0);
		}
		add((i - 1) * n + n, i * n + n, 1e8);
		add(i * n + n, (i - 1) * n + n, 0);
	}

	// 计算最小割
	s_node = 1, e_node = (val - 1) * n + n;
	return dinic() <= k;
}

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

	cin >> n >> m >> k;
	for (int i = 1; i <= m; ++i) cin >> s_nodes[i] >> e_nodes[i];

	int l = 0, r = n - 1;
	while (l < r) {
		int mid = l + r + 1 >> 1;
		// 当前最短路是mid情况下, 满足修改边的数量 <= k
		if (check(mid)) l = mid;
		else r = mid - 1;
	}

	cout << l << "\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值