NZOJ 模拟赛6

news/2024/10/10 20:41:12

T1 叉叉

现在有一个字符串,每个字母出现的次数均为偶数。接下来我们把第一次出现的字母a和第二次出现的a连一条线,第三次出现的和四次出现的字母a连一条线,第五次出现的和六次出现的字母a连一条线...对其他25个字母也做同样的操作。

现在我们想知道有多少对连线交叉。交叉的定义为一个连线的端点在另外一个连线的内部,另外一个端点在外部。

下图是一个例子,共有三对连线交叉(我们连线的时候,只能从字符串上方经过)。

一行一个字符串。保证字符串均由小写字母组成,且每个字母出现次数为偶数次。

对于100%的数据,字符串长度不超过100,000。

image

事实上 \(O(n^2)\) 的暴力都能够通过此题,也许是字符集过小使得期望复杂度优化到了 \(O(n\sigma)\),如果 \(\sigma\) 够大这样的算法就无法通过,所以我们来谈谈正解。

我们来考虑整个字符串匹配的过程,可以得到许多个字符串,你想要求有多少对字符串相交了,从左到右的扫描过程中,我们每遇到一个左端点,存入 \(last\) 数组中,下次遇到右端点的时候清空 \(last\),并且遇到左端点,对后继答案贡献 \(+1\),遇到右端点对后继答案 \(-1\)

这样操作的一个好处是,我们只考虑的单侧的答案,因此对于两条线段相交,一定不会被重复统计两次,接下来考虑处理答案的问题。

匹配过程中,对于当前线段 \([l, ~ r]\),考虑对 \([l, ~ r]\) 内未被匹配的点的数量求和,因此我们可以用树状数组来优化。

当遇到线段的左端点时,在左端点处 \(+1\),遇到右端点时,在左端点处 \(-1\),每次匹配后求出 \([l, ~ r]\) 的子段和即可。

时间复杂度 \(O(n\log{n})\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, last[26];
int tr[N];
ll ans;
char a[N];int lowbit(int x)
{return x & -x;
}void add(int x, int d)
{for (int i = x; i <= n; i += lowbit(i)) tr[i] += d;
}int sum(int x)
{int res = 0;for (int i = x; i; i -= lowbit(i)) res += tr[i];return res;
}int main()
{cin >> (a + 1), n = strlen(a + 1);for (int i = 1; i <= n; i ++ ){if (last[a[i] - 'a']){add(last[a[i] - 'a'], -1);ans += sum(i) - sum(last[a[i] - 'a']);last[a[i] - 'a'] = 0;}else last[a[i] - 'a'] = i, add(i, 1);}cout << ans;return 0;
}

T2 跳跳虎回家

跳跳虎在外面出去玩忘了时间,现在他需要在最短的时间内赶回家。
跳跳虎所在的世界可以抽象成一个含有n个点的图(点编号从1到n),跳跳虎现在在1号点,跳跳虎的家在n号点。
图上一共有m条单向边,通过每条边有固定的时间花费。
同时,还存在若干个单向传送通道,传送通道也有其时间花费。
传送通道一般来说比普通的道路更快,但是跳跳虎最多只能使用k次。
跳跳虎想知道他回到家的最小时间消耗是多少。

第一行输入4个整数n,m,q,k。(n表示点数,m表示普通道路的数量,q表示传送通道的数量,k表示跳跳虎最多使用k次传送通道)
接下来m行每行3个整数u,v,w,表示有一条从u到v,时间花费为w的普通道路。(1≤u,v≤n,1≤w≤10^3)
接下来q行每行3个整数x,y,z,表示有一条从x到y,时间花费为z的传送通道。(1≤x,y≤n,1≤z≤10^3)

对于100%的数据,1≤n≤500,0≤m,q≤2000,0≤k≤10^9。

考虑建立一个分层图模型,什么是分层图呢?

picture

在原有图的基础上,我们将这个图复制多次,产生一个层状结构,每次利用通道相当于向下一层,最多经历 \(k\) 层就可以到达底部,由于 \(q \le 2000\),因此 \(k\) 取得再大也超过 \(q\) 也是无效的,因为对于同一个通道,我使用两次一定是不优的,于是可以写出本题代码。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 1000, INF = 0x7f7f7f7f;
int n, m, q, t;
int h[N], e[N << 4], ne[N << 4], w[N << 4], idx;
int dist[N], st[N];void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}void dijkstra(int S)
{memset(dist, 0x7f, sizeof dist);priority_queue<PII, vector<PII>, greater<PII>> heap;heap.push({0, S}), dist[S] = 0;while (heap.size()){PII t = heap.top();heap.pop();int u = t.second, d = t.first;if (st[u]) continue;st[u] = 1;for (int i = h[u]; ~i; i = ne[i]){int j = e[i];if (dist[j] > d + w[i]){dist[j] = d + w[i];heap.push({dist[j], j});}}}
}int main()
{memset(h, -1, sizeof h);scanf("%d%d%d%d", &n, &m, &q, &t);t = min(q, t);for (int i = 1; i <= m; i ++ ){int a, b, c;scanf("%d%d%d", &a, &b, &c);for (int j = 0; j <= t; j ++ )add(a + j * n, b + j * n, c);}for (int i = 1; i <= q; i ++ ){int a, b, c;scanf("%d%d%d", &a, &b, &c);for (int j = 0; j < t; j ++ )add(a + j * n, b + (j + 1) * n, c);}dijkstra(1);int ans = INF;for (int i = 0; i <= t; i ++ ) ans = min(ans, dist[n * (i + 1)]);if (ans == INF) ans = -1;printf("%d", ans);return 0;
}

然后兴高采烈地交一发,美妙的 TLE 了,用了 1.2s,推测可能是将整个图建出来后 \(n\) 太大,导致 dijkstra 的 \(O(n\log{n})\) 复杂度常数太大。

我们考虑在原图上跑 dijkstra,记录 \(dist[i][j]\) 表示到 \(i\) 点用了 \(j\) 次快速通道的最短路,常数可以变小,因而可以通过此题。

为了实现这个我们需要将边分类为两种,跑第一种的时候考虑同层最短路,跑第二种的时候考虑向下转移,\(st\) 数组开到二维即可,具体实现如下。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, PII> PPI;
const int N = 510, M = 2010, INF = 0x7f7f7f7f;
int n, m, q, t;
int h[N], is[M << 1], e[M << 1], ne[M << 1], w[M << 1], idx;
int dist[N][M], st[N][M];void add(int a, int b, int c, int d)
{e[idx] = b, w[idx] = c, is[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
}void dijkstra(int S)
{memset(dist, 0x7f, sizeof dist);priority_queue<PPI, vector<PPI>, greater<PPI>> heap;heap.push({0, {S, 0}}), dist[S][0] = 0;while (heap.size()){PPI top = heap.top();heap.pop();int u = top.second.first, d = top.first, k = top.second.second;if (st[u][k]) continue;st[u][k] = 1;for (int i = h[u]; ~i; i = ne[i]){int j = e[i];if (!is[i]){if (dist[j][k] > d + w[i]){dist[j][k] = d + w[i];heap.push({dist[j][k], {j, k}});}}else if (k < t){if (dist[j][k + 1] > d + w[i]){dist[j][k + 1] = d + w[i];heap.push({dist[j][k + 1], {j, k + 1}});}}}}
}int main()
{memset(h, -1, sizeof h);scanf("%d%d%d%d", &n, &m, &q, &t);t = min(q, t);for (int i = 1; i <= m; i ++ ){int a, b, c;scanf("%d%d%d", &a, &b, &c);add(a, b, c, 0);}for (int i = 1; i <= q; i ++ ){int a, b, c;scanf("%d%d%d", &a, &b, &c);add(a, b, c, 1);}dijkstra(1);int ans = INF;for (int i = 0; i <= t; i ++ ) ans = min(ans, dist[n][i]);if (ans == INF) ans = -1;printf("%d", ans);return 0;
}

事实上这个也跑了 700ms,小卡了一波常数,不过分层图的思想是需要掌握的。

时间复杂度 \(O((n + m + q)\min{(k, ~ q)}\log{(m + q)})\)

T3 树

给定一棵n个节点,以S为根的树,边有权,删掉一条边的代价为这条边的边权。现在你要删除若干条边,使得根节点S不能到达任何叶子节点。请求出最小的代价和。

image

\(dp[i]\) 表示以 \(i\) 为根的子树无法到达任何叶子节点的最小代价,考虑如何向父亲转移。

可以发现,父亲 \(u\) 无法到达该子树的任何叶子结点的最小代价应该为 \(\min{(dp[i], w[u][i])}\),其中 \(w[u][i]\) 表示 \(u \to i\) 的边权。

那么 \(dp[u]\) 可以很快得到 \(dp[u] = \sum\limits_{i \in son[u]} \min{(dp[i], w[u][i])}\)

只需要初始化叶子节点为 \(inf\),所有其他节点为 \(0\) 即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, S;
int h[N], e[N << 1], ne[N << 1], idx;
ll w[N << 1], dp[N];void add(int a, int b, ll c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}void dfs(int u, int f)
{if (ne[h[u]] == -1 && e[h[u]] == f) return dp[u] = 1e18, void();for (int i = h[u]; ~i; i = ne[i]){int j = e[i];if (j == f) continue;dfs(j, u);dp[u] += min(w[i], dp[j]);}
}int main()
{cin >> n >> S;memset(h, -1, sizeof h);for (int i = 1; i < n; i ++ ){int a, b;ll c;cin >> a >> b >> c;add(a, b, c), add(b, a, c);}dfs(S, 0);cout << dp[S];return 0;
}

T4 阶乘

求出n的阶乘在k进制表示下末尾0的个数。

对于100%的数据,\(n \le 10^{12}, ~ k \le 10^{12}\)

考虑如何在 \(k\) 进制下出现 \(0\),那必然是可以将 \(n\) 表示为 \(N \times k^t(N, ~ k, ~ t \in N^*)\) 的形式,我们需要求出这个 \(t\)

我们可以考虑将 \(k\) 质因数分解为 \(k = p_{1}^{\alpha_1}p_{2}^{\alpha_2}...p_{s}^{\alpha_s}\)

现在问题就转化为求这些质数在 \(n!\) 中出现了多少次了。

让我们介绍勒让德定理

\(n!\) 的唯一分解中,对于质数 \(p\),记 \(L_p(n!)\) 为素数 \(p\) 的最高指数,这里的 \(L_p(n!)\) 为勒让德函数。

勒让德定理

\[L_p(n!) = \sum_{k \ge 1}\bigg\lfloor\frac{n}{p^k}\bigg\rfloor \]

证明

\(N_p(n!, ~ k)\) 表示 \([1, ~ n!]\) 中唯一分解后素数 \(p\) 的幂为 \(k\) 的数个数。

易知 \(L_p(n!) = N_p(n!, ~ 1) + 2N_p(n!, ~ 2) + ... + rN_p(n!, ~ r)\)

而对于 \([1, n]\) 中能被 \(p\) 整除的数有 \(\lfloor\frac{n}{p}\rfloor\) 个,即 \(N_p(n!, ~ 1) + N_p(n!, ~ 2) + ... + N_p(n!, ~ r)\) 个。

对于 \([1, n]\) 中能被 \(p^k\) 整除的数有 \(\lfloor\frac{n}{p^k}\rfloor\) 个,即 \(N_p(n!, ~ k) + N_p(n!, ~ k + 1) + ... + N_p(n!, ~ r)\) 个。

综上可知:

\[\begin{aligned}L_p(n!) &= N_p(n!, ~ 1) + 2N_p(n!, ~ 2) + ... + rN_p(n!, ~ r)\\&= \sum\limits_{k \ge 1}\bigg\lfloor\frac{n}{p^k}\bigg\rfloor\end{aligned}\]

证毕。

因此此题我们找出 \(k = p_{1}^{\alpha_1}p_{2}^{\alpha_2}...p_{s}^{\alpha_s}\) 中,对任意 \(p_i\)\(L_{p_i}(n!)\),将 \(L_{p_i}(n!)\)\(p_i\) 分配到 \(k\) 上,每 \(\alpha_i\)\(p_i\) 可能产生一个 \(k\) 的因子,即对末尾 \(0\) 做出贡献。

所以我们的答案为表达式如下:

\[ans = \min_{i = 1}^{s}\bigg\lfloor\frac{L_{p_i}(n!)}{\alpha_i}\bigg\rfloor \]

分解 \(k\) 需要注意可能有大素数,分解出 \(\sqrt{k}\) 以下的质数后特判以下即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
ll n, k, ans = 1e12;
ll primes[N], num[N], cnt;
bool st[N];void init(int n)
{for (int i = 2; i <= n; i ++ ){if (!st[i]) primes[ ++ cnt] = i;for (int j = 1; primes[j] <= n / i; j ++ ){st[primes[j] * i] = 1;if (i % primes[j] == 0) break;}}
}int main()
{cin >> n >> k;init(sqrt(k));for (int i = 1; i <= cnt; i ++ )while (k % primes[i] == 0)k /= primes[i], num[i] ++ ;for (int i = 1; i <= cnt; i ++ ){ll res = 0;if (num[i]){ll t = 1;while (n / primes[i] >= t)t *= primes[i], res += n / t;ans = min(ans, res / num[i]);}}ll res = 0;if (k != 1) {ll t = 1;while (n / k >= t)t *= k, res += n / t;ans = min(ans, res);}cout << ans;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/69938.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

markdown的html优雅使用语法(2024/10/10guixiang原创)

一:图片部分 第一范式图 2 全字段排序<center><img style="border-radius: 0.3125em;box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);" width = "500" height = "400"src="https://static001.geek…

本地项目apollo的注册中心地址写的是ECS公网地址,但是项目启动后,发现它去拉取配置的时候变成了私网地址?

代码截图报错: 配置的注册中心是公网的地址,拉取配置的时候竟然变成私网了,就很离谱,后来通过网上查找资料,很多奇怪的解决方案都试了没有解决,后来发现了一篇文章,只需要在启动参数中配上:apollo.config-service 参数即可,配置截图如下:-Ddev=DEV 用于指定apollo中对…

[笔记]数据库内核杂谈系列笔记

知识点:数据库 --> 分布式数据库 --> 数据模型/数据湖 --> 其他数据库数据库的核心就在于一条query的执行流程 从这个开始,慢慢的拆解 如何将一个普通数据库转化为分布式数据库? 数据模型/数据库 -数据的组织形式 向量数据库1.一小时实现基本功能的数据库 数据库最…

面向法律领域的大模型微调与应用

来源:论文摘要 这篇论文探讨了如何通过微调大型语言模型来实现法律领域的智能化服务。作者指出,以往的智慧法律系统需要为每个特定的任务设计专门的算法或模型,面临着研发成本高、集成难度大的困难。而现在,通过微调大型语言模型,可以同时处理多种法律任务,从而提高法律从…

招标书样本

本文来自博客园,作者:ukyo--夜王,转载请注明原文链接:https://www.cnblogs.com/ukzq/p/18457050

Gradio 5 稳定版正式发布

在过去的几个月里,我们一直在努力工作,今天,我们想向大家展示成果:Gradio 5 稳定版现已发布。 有了 Gradio 5,开发者可以构建 生产级的机器学习 Web 应用,这些应用不仅性能优越、可扩展、设计精美、易于访问,而且还遵循了最佳的 Web 安全实践。更重要的是,只需几行 Pyt…

Odoo18.0 支付宝

odoo16.0官方将支付宝模块弃用了,但是需求还在,我们也不太可能去使用推荐的asipay,因此,我们决定,自己手动实现它。 安装与配置 首先,我们在应用中安装我们的支付宝模块:安装好之后,我们在销售-配置-在线支付-支付提供商菜单进入支付宝配置界面: 这里对各个参数简单介…

【Azure Event Hub】诡异现象之Event Hub无法删除的根源

问题描述 遇见一个诡异的现象。在Event Hub 事件中心中删除了一个Event Hub后,会立马被重建,多次删除发现都是同样的问题。 这是什么情况呢? 问题解答 经过对Event Hub调查发现,使用了Kafka客户端持续的发送/消费事件。而Kafka客户端自带属性auto.create.topics.enable = …