http连接复用进化论 Git常用命令超级详细(全网最详细) Android系统“资源调度框架” 进来偷学一招,数据归档二三事儿 安卓手机改造服务器——基本环境配置(CentOS7 arm32) [Kick Start] 2021 Round B 对抗样本综述(一) 海量数据Excel报表利器——EasyExcel(一 利用反射机制导出Excel) 《面试补习》-熔断降级我学会了! 完了,又火一个项目 Vue 两个字段联合校验典型例子--修改密码 ES2021 新特性! (数据科学学习手札124)pan
Increasing Substring
输出字符串中每个字符的最长 Increasing Substring 的长度,非常简单的动态规划问题。
定义 dp[i]
是以 str[i]
结尾的最长 Increasing Substring 的长度。
转移方程
dp[i] = dp[i-1] + 1, if str[i-1] < str[i]
dp[i] = 1, otherwise
显然是可以进行空间优化的,然而「可以但没必要」。
代码实现
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int cnt = 1;
void solve(string &str, int n)
{
vector<int> dp(n, 1);
for (int i = 1; i < n; i++)
{
if (str[i - 1] < str[i])
dp[i] = dp[i - 1] + 1;
}
printf("Case #%d:", cnt++);
for (int x : dp) printf(" %d", x);
printf("\n");
}
int main()
{
int t, n;
cin >> t;
cin.ignore();
while (t--)
{
string str;
cin >> n; cin.ignore();
cin >> str; cin.ignore();
solve(str, n);
}
}
Longest Progression
给定一个数组 \(A[n]\) ,在允许改动一个元素的条件下,找到最长的等差数列的长度(这个数列在数组中必须是连续的)。
令:
-
left[i]
表示从位置i
向左延伸,能够得到的最长等差数列的长度(包含a[i]
); -
right[i]
表示从位置i
向右延伸,能够得到的最长等差数列的长度(包含a[i]
)。
显然,如果我们允许改动一个位置,那么扫描数组中的任意一个数 \(a_i\) ,判断改动 \(a_i\) 是否能组合得到一个更长的等差数列:
- 组合
left[i-1]
和right[i+1]
- 条件为:\(a_{i+2} – a_{i+1} = a_{i-1} – a_{i-2} \text{ and } a_{i+1} – a_{i-1} = 2(a_{i-1} – a_{i-2})\)
- 组合
left[i-1]
和a[i], a[i+1]
- 条件为:\(a_{i+1} – a_{i-1} = 2(a_{i-1} – a_{i-2})\)
- 组合
right[i+1]
和a[i], a[i-1]
- 条件为:\(a_{i+1} – a_{i-1} = 2(a_{i+2} – a_{i+1})\)
- 组合
left[i-1]
和a[i]
,或者组合right[i+1]
和a[i]
,二者是必然能实现的,无需任何条件。
代码实现
#include <iostream>
#include <vector>
using namespace std;
int cnt = 1;
int solve(int n, vector<int> &a)
{
if (n <= 3) return n;
vector<int> left(n, 2), right(n, 2);
left[0] = right[n - 1] = 1;
for (int i = 2; i < n; i++)
if ((a[i] - a[i - 1]) == (a[i - 1] - a[i - 2]))
left[i] = left[i - 1] + 1;
for (int i = n - 3; i >= 0; i--)
if ((a[i + 2] - a[i + 1]) == (a[i + 1] - a[i]))
right[i] = right[i + 1] + 1;
int ans = max(left[n - 2] + 1, right[1] + 1);
for (int i = 1; i < n - 1; i++)
{
// left[i-1] + 1 其实就是组合 left[i-1] 和 a[i], 因为允许改动 a[i], 所以这是必然能实现的
// right[i+1] 与之同理
ans = max(ans, max(left[i - 1] + 1, right[i + 1] + 1));
if (i >= 2 && a[i + 1] - a[i - 1] == 2 * (a[i - 1] - a[i - 2]))
ans = max(ans, left[i - 1] + 2);
if (i + 2 < n && a[i + 1] - a[i - 1] == 2 * (a[i + 2] - a[i + 1]))
ans = max(ans, right[i + 1] + 2);
if (i >= 2 && i + 2 < n &&
a[i + 1] - a[i - 1] == 2 * (a[i - 1] - a[i - 2]) &&
a[i - 1] - a[i - 2] == a[i + 2] - a[i + 1])
ans = max(ans, left[i - 1] + right[i + 1] + 1);
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
int t, n;
cin >> t;
while (t--)
{
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) cin >> nums[i];
printf("Case #%d: %d\n", cnt++, solve(n, nums));
}
}
Consecutive Primes
给定一个整数 \(n\) ,求两个相邻的素数 \(l, r\) ( \(l \cdot r \le n\) ) ,且使得 \(l \cdot r\) 的乘积最大,输出这个最大乘积。
思路
- 令 \(k = \sqrt{n}\) , 求出 \(k\) 左侧的最大素数为 \(l\) ,\(k\) 右侧的最小素数为 \(r\) 。
- 如果 \(l \cdot r \le n\) ,那么返回 \(l \cdot r\) 。
- 否则,存在 \(l_2 < l\) ,\(l_2\) 是小于 \(l\) 的最大素数,返回 \(l_2 \cdot l\) 。
正确性证明
- 最理想的情况是 \(k = \sqrt{n}\) 为一个整数,那么 \(l = r = \sqrt{n}\) 可以得到最大乘积 \(n\) 。但题目要求为 2 个相邻的不同素数。
- 因此,这 2 个素数必然是下面 2 种情况之一(否则不能保证 \(l \cdot r \le n\) ):
- 一个在 \(k\) 的左侧,一个在 \(k\) 的右侧。
- 两个都在 \(k\) 的左侧。
- 显然,如果「一左一右」的情况存在,它的乘积必然大于「均在左侧」这个乘积,因为 \(l_2 < l < r\)。
时间复杂度
- 素数判定可以在 \(O(\sqrt{k})\) 内完成。
- 根据 Prime Gap ,两个相邻素数 \(p_{i}, p_{i+1}\) 之差可以记为 \(g_i\) .
- 最坏情况下,我们需要找到 3 个相邻的素数(需要扫描 2 个 Prime Gap),因此算法复杂度为 \(O((g_l + g_r) \cdot \sqrt{k})\),\(k = \sqrt{n}\) .
- 题目给定 \(n \le 10^{18}\) ,因此 \(k \le 10^9\) 。查表得 \(g_l, g_r\) 在 282 – 288 之间,因而这一算法复杂度是可以接受的。
代码实现
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int cnt = 1;
bool isprime(uint64_t k)
{
for (uint64_t i = 2; i * i <= k; i++)
if (k % i == 0) return false;
return true;
}
uint64_t solve(uint64_t n)
{
uint64_t k = sqrt(n);
uint64_t l = k, r = k + 1;
while (!isprime(l)) l--;
while (!isprime(r)) r++;
if (l * r <= n) return l * r;
uint64_t l2 = l - 1;
while (!isprime(l2)) l2--;
return l * l2;
}
int main()
{
int t;
cin >> t;
cin.ignore();
while (t--)
{
uint64_t n;
cin >> n;
cin.ignore();
printf("Case #%d: %llu\n", cnt++, solve(n));
}
}
Truck Delivery
给定一个树 \(G\) ,每个顶点 \(1-n\) 代表一个城市,每个边代表一个公路,公路有 2 个参数 (limit, amount)
。如果经过这一公路的卡车,它的 weight
大于等于 limit
,那么需要收费 amount
,否则不收费。
问:给定一个 \(Q\) ,表示工作的天数,每一天有 2 个参数 \(Q_i = (C, W)\) ,表示从城市 \(C\) 出发,目的地是城市 \(1\) ,显然这样的路径是唯一的。卡车的 weight
为 \(W\) ,那么从 \(C \rightarrow 1\) 的这一路径上,每个公路都对应一个收费。对于每个 \(Q_i\),求这一天中,所有收费的最大公因子。
BFS/DFS
最简单,也是最暴力的解法。
思路
-
建图完成后,执行
bfs(1)
,从城市 \(1\) 开始 BFS,找到 \(1\) 到其他城市 \(2-n\) 的所有路径。路径通过一个数组pre
记录,pre[x]
表示x
的前驱城市。 -
对于每一个 \(Q_i = (C,W)\) ,找到从 \(C \rightarrow 1\) 的路径,并找到所有
amount
的最大公因子。 -
时间复杂度为 \(O(N + Q(N + \log{A}))\) , \(A\) 是
amount
的最大值。 -
显然,这个复杂度对于 Test Case 2 来说是不可接受的。
-
第一步的 BFS 也可以换成 DFS ,因为在树中,只要遍历一次,即可找到 \(1\) 到 \(2-n\) 的路径。
代码实现