[补题记录] Atcoder Beginner Contest 324(E、F)

URL:https://atcoder.jp/contests/abc324

[补题记录] Atcoder Beginner Contest 324(E、F)_第1张图片

目录

E

Problem/题意

Thought/思路

Code/代码

F

Problem/题意

Thought/思路

Code/代码


E

Problem/题意

给出 N 个字符串和 1 个 T 字符串,都由小写字母组成。

现在从 N 个字符串中任取 2 个拼接,如果拼接后的字符串的子序列包含 T 字符串,则这是一种合法拼接。

问有多少种拼接方案。

Thought/思路

每种方案都只能由两个字符串组成,很容易想到:是否能求出有多少个字符串能作为前缀,有多少个字符串能作为后缀。

假设字符串 S[i] 在 T 中的前缀长度为 pre[i],在 T 中的后缀长度为 suf[i]。

那么对于一个二元组(i,j),只要 pre[i] + suf[j] >= len(T),就能作为一种方案。

但是一一匹配肯定是超时的,考虑到遍历过程中我们知道 pre[i] 和 suf[i],就能算出一个值:len(T) - pre[i]。这个值代表着,只要后缀长度大于等于它的,都能作为一次答案。

因此,我们只需要多维护一个 count[i],表示后缀长度为 i 的个数即可,最后的答案就是:

\sum_{i=1}^{N}\sum_{j=\left | T \right |-pre[i]}^{\left | T \right |}count[j]

至于这双重循环会不会超时,把 count[j] 当作 1,展开算一下就知道了。

Code/代码

#include "bits/stdc++.h"

#define int long long

int n, pre[500007], suf[500007],count[500007];
std::string T;

int subSequence(std::string s) {
	int res = 0, len = s.length();
	for (int i = 0, j = 0; i < len; ++ i) {
		if (s[i] == T[j]) j ++, res = j;
	}
	return res;
}

signed main() {
	std::cin >> n >> T;

	std::vector  S(n + 1);
	for (int i = 1; i <= n; ++ i) {
		std::cin >> S[i];
		pre[i] = subSequence(S[i]);
	}

	std::reverse(T.begin(), T.end());

	for (int i = 1; i <= n; ++ i) {
		std::reverse(S[i].begin(), S[i].end());
		suf[i] = subSequence(S[i]);
		count[suf[i]] ++;
	}

	int ans = 0;
	for (int i = 1; i <= n; ++ i) {
		int r = T.length();
		for (int j = r - pre[i]; j <= r; ++ j) {
			ans += count[j];
		}
	}

	std::cout << ans;
}

F

Problem/题意

给出一个有向图,每条边有两个值 b 和 c。问从 1 到 N 的路线中,那条路线的 ∑b / ∑c 最大,求出这个最大值。

Thought/思路

分数规划 \frac{\sum b_{i}}{\sum c_{i}} \geqslant x 或 \frac{\sum b_{i}}{\sum c_{i}} \leqslant x

在这简单回忆一下分数规划:

因为 \frac{\sum b_{i}}{\sum c_{i}} 在题目环境下通常都有 min、max 值,所以可以二分 X ∈ [min, max]。

一般来说,会以 b_{i}-x*c_{i} 作为不同题目环境下的权值。(选物品就类似价值,图论就类似边权)

而对于某个 X,我们需要回到题目中明确约束条件,寻找适合 X 的二分条件,选择合适的算法求出:

\sum b_{i}-x*c_{i}

[补题记录] Atcoder Beginner Contest 324(E、F)_第2张图片


在这道题中,我们需要求出 \frac{\sum b_{i}}{\sum c_{i}} 的最大值,所以要将 x 最大化,从而得出:

\sum b_{i}-x*c_{i} \geqslant 0

而要满足这个条件,最低要求只需要一条最长路 dis[n] >= 0 即可。(因为在题目中 \frac{\sum b_{i}}{\sum c_{i}} 相当于是边权求和,所以是最长路)

但是求最长路比较麻烦,我们加个负号将其改为最短路:

\sum x*c_{i} - b_{i} \leqslant 0

这时结果判断也要改成 dis[n] <= 0。

Code/代码

#include "bits/stdc++.h"

#define int long long

const int inf = 1e9;

struct node {
	int e, b, c;
	double d;
};

int n, m;
double dis[200007];
std::vector  g[200007];

bool check(double x) {
	// 构建边权
	for (int i = 1; i <= n; ++ i) {
		dis[i] = inf;
		for (int j = 0; j < g[i].size(); ++ j) {
			g[i][j].d = -(g[i][j].b - x * g[i][j].c);
		}
	}

	// 求最短路
	dis[1] = 0;
	for (int i = 1; i <= n; ++ i) {
		for (int j = 0; j < g[i].size(); ++ j) {
			node next = g[i][j];
			dis[next.e] = std::min(dis[next.e], next.d + dis[i]);
		}
	}

	return dis[n] <= 0; // 加了负号,这里也要改
}

signed main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(0); std::cout.tie(0);

	std::cin >> n >> m;
	for (int i = 1; i <= m; ++ i) {
		int x, y, b, c;
		std::cin >> x >> y >> b >> c;
		g[x].push_back({y, b, c, 0.0});
	}

	double l = 0, r = 10000;
	while (r - l > 1e-10) {
		double mid = (r + l) / 2;
		if (check(mid)) l = mid;
		else r = mid;
	}

	std::cout << std::fixed << std::setprecision(10) << l;
}

你可能感兴趣的:(补题记录,算法,c++,动态规划)