AcWing 1165 单词环
单词环
一、题目描述
我们有 个字符串,每个字符串都是由 ∼ 的 小写英文字母 组成的。
如果字符串 的结尾两个字符刚好与字符串 的 开头两个字符相匹配,那么我们称 与 能够相连(注意: 能与 相连不代表 能与 相连)。
我们希望从给定的字符串中找出一些,使得它们首尾相连形成一个 环串(一个串首尾相连也算),我们想要使这个环串的 平均长度最大 。
如下例:
ababc
bckjaca
caahoynaab
第一个串能与第二个串相连,第二个串能与第三个串相连,第三个串能与第一个串相连,我们按照此顺序相连,便形成了一个环串,长度为 (重复部分算两次),总共使用了 个串,所以平均长度是 。
输入格式
本题有多组数据。
每组数据的第一行,一个整数 ,表示字符串数量;
接下来 行,每行一个长度小于等于 的字符串。
读入以 结束。
输出格式
若不存在环串,输出 No solution
,否则输出最长的环串的平均长度。
只要答案与标准答案的差不超过 0.01
,就视为答案正确。
数据范围
输入样例:
3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0
输出样例:
21.66
二、题目解析
1、建图技巧
本题同样是考察 分数规划,只不过难度较上题有所提升。首先需要考虑的是 如何建图,如果常规的用每个字符串作为图中的节点,最多会有个节点,边数也可能高达级别,显然难以承受,而且以字符串作为节点,还需要挨个比较每个字符串的末尾两个字符与其它字符串的开头两个字符是否相等,这也将耗费大量的时间。
我们知道,有小写字母构成的两个字符最多有种可能,而这十万字符串中间的内容并不重要,我们只关心每个字符串的 开始两个字符、末尾两个字符 以及 字符串的长度。这样我们读入一个字符串时,将其开头长度为的字符串作为一个节点,末尾长度为的字符串作为另一个节点,两个节点间有向边的长度就是该字符串的长度。这种 巧妙 的建图方式不仅使得节点数骤减,边数不超过限制,更方便的是每条边都象征着存在一个字符串开头字符串是,末尾字符串是,字符串长度是。如果指向另一个点,就自然的连接起来了,并且连接后字符串的长度就是边权之和,非常方便。既然节点最多有种可能性,那么就可以将长度为的字符串进行哈希映射到到的区间中,比如就映射到编号为的节点,这样本题的图就建好了。
2、分数规划
第二步就是按照分数规划的解题思路推公式,环串的平均长度最大,等价于最大,其中
- 表示环中各边的长度累加和
- 就是环上的 边数 累加和
要判断对于某个是否有,只需要,即即可,即边权为的图中存在负权回路,问题就进一步转化为了求负权回路问题了。
注意:此题的数据卡了的,可以用的方法判断是否存在负环。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
const int M = 1e5 + 10;
const int U = 26 * 26;
int n;
int h[N], e[M], w[M], ne[M], idx;
double dist[N];
bool st[N]; // 如果当前这个点 u 可以松弛的点 v 是被访问过的,那么就说明一定存在负环。
char str[N]; // 黄海替换为string,结果执行时间由70ms--> 400ms!!!
const double eps = 1e-3;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int u, double mid) {
if (st[u]) return 1; // 如果又见u,说明有环
bool flag = 0; // 我的后代们是不是有环?
st[u] = 1; // u我出现过一次了~
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
// 更新最小值,判负环
if (dist[v] > dist[u] - w[i] + mid) {
dist[v] = dist[u] - w[i] + mid;
// 检查一下我的下一个节点j,它要是有负环检查到,我也汇报
flag = dfs(v, mid);
if (flag) break;
}
}
st[u] = 0; // 回溯
return flag;
}
bool check(double mid) {
memset(dist, 0, sizeof dist);
for (int i = 0; i < U; i++)
if (dfs(i, mid)) return 1;
return 0;
}
void solve() {
memset(h, -1, sizeof h);
idx = 0;
while (n--) {
scanf("%s", str);
int len = strlen(str);
if (len < 2) continue;
int a = (str[0] - 'a') * 26 + str[1] - 'a';
int b = (str[len - 2] - 'a') * 26 + str[len - 1] - 'a';
add(a, b, len);
}
double l = 0, r = 1e3;
while (r - l > eps) {
double mid = (l + r) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
if (l < eps)
puts("No solution");
else
printf("%.2lf\n", r);
}
int main() {
// 多组测试数组,喜欢单开一个solve()进行处理,确实看着舒服
while (scanf("%d", &n), n) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2019-03-26 IDEA手动创建JFinal项目
2019-03-26 工作流选型
2019-03-26 记录sql server中数据创建时间和最后修改时间,方便查找问题