[Kick Start] 2021 Round B

题目:Kick Start 2021 Round-B .

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] )。

显然,如果我们允许改动一个位置,那么扫描数组中的任意一个数 ai ,判断改动 ai 是否能组合得到一个更长的等差数列:

  • 组合 left[i-1]right[i+1]
    • 条件为:ai+2ai+1=ai1ai2 and ai+1ai1=2(ai1ai2)
  • 组合 left[i-1]a[i], a[i+1]
    • 条件为:ai+1ai1=2(ai1ai2)
  • 组合 right[i+1]a[i], a[i-1]
    • 条件为:ai+1ai1=2(ai+2ai+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 ( lrn ) ,且使得 lr 的乘积最大,输出这个最大乘积。

思路

  • k=n , 求出 k 左侧的最大素数为 lk 右侧的最小素数为 r
  • 如果 lrn ,那么返回 lr
  • 否则,存在 l2<ll2 是小于 l 的最大素数,返回 l2l

正确性证明

  • 最理想的情况是 k=n 为一个整数,那么 l=r=n 可以得到最大乘积 n 。但题目要求为 2 个相邻的不同素数。
  • 因此,这 2 个素数必然是下面 2 种情况之一(否则不能保证 lrn ):
    • 一个在 k 的左侧,一个在 k 的右侧。
    • 两个都在 k 的左侧。
  • 显然,如果「一左一右」的情况存在,它的乘积必然大于「均在左侧」这个乘积,因为 l2<l<r

时间复杂度

  • 素数判定可以在 O(k) 内完成。
  • 根据 Prime Gap ,两个相邻素数 pi,pi+1 之差可以记为 gi .
  • 最坏情况下,我们需要找到 3 个相邻的素数(需要扫描 2 个 Prime Gap),因此算法复杂度为 O((gl+gr)k)k=n .
  • 题目给定 n1018 ,因此 k109 。查表得 gl,gr 在 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 ,每个顶点 1n 代表一个城市,每个边代表一个公路,公路有 2 个参数 (limit, amount) 。如果经过这一公路的卡车,它的 weight 大于等于 limit ,那么需要收费 amount ,否则不收费。

问:给定一个 Q ,表示工作的天数,每一天有 2 个参数 Qi=(C,W) ,表示从城市 C 出发,目的地是城市 1 ,显然这样的路径是唯一的。卡车的 weightW ,那么从 C1 的这一路径上,每个公路都对应一个收费。对于每个 Qi,求这一天中,所有收费的最大公因子。

BFS/DFS

最简单,也是最暴力的解法。

思路

  • 建图完成后,执行 bfs(1) ,从城市 1 开始 BFS,找到 1 到其他城市 2n 的所有路径。路径通过一个数组 pre 记录,pre[x] 表示 x 的前驱城市。

  • 对于每一个 Qi=(C,W) ,找到从 C1 的路径,并找到所有 amount 的最大公因子。

  • 时间复杂度为 O(N+Q(N+logA)) , Aamount 的最大值。

  • 显然,这个复杂度对于 Test Case 2 来说是不可接受的。

  • 第一步的 BFS 也可以换成 DFS ,因为在树中,只要遍历一次,即可找到 12n 的路径。

代码实现

🥸 被数据范围搞死了,amount 的范围是 1018 ,因此必须用 uint64_t ,我改了数据范围,但忘了改 ans 的类型;改了 ans 的类型,但忘了改 gcd 的参数;改了 gcd 的参数,但忘了改 gcd 的返回值。最后逼得我 Ctrl+F 把 int 全部替换为 uint64_t

#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>
using namespace std;
// 'first' is load-limit, 'second' is amount
typedef pair<uint64_t, uint64_t> node_t;
uint64_t cnt = 1;
unordered_map<uint64_t, unordered_map<uint64_t, node_t>> graph;
uint64_t gcd(uint64_t a, uint64_t b) { return b == 0 ? a : gcd(b, a % b); }
vector<uint64_t> bfs(uint64_t n, uint64_t start)
{
    vector<uint64_t> vis(n + 1, false), pre(n + 1, 0);
    queue<uint64_t> q;
    vis[start] = true, q.push(start);
    while (!q.empty())
    {
        uint64_t vex = q.front();
        q.pop();
        for (auto &p : graph[vex])
        {
            uint64_t adjacent = p.first;
            if (!vis[adjacent])
                vis[adjacent] = true, q.push(adjacent), pre[adjacent] = vex;
        }
    }
    return move(pre);
}

int main()
{
    ios::sync_with_stdio(0);
    uint64_t t, n, q;
    uint64_t x, y, limit, amount, city, weight;
    cin >> t;
    while (t--)
    {
        cin >> n >> q;
        graph.clear();
        for (uint64_t i = 0; i < n - 1; i++)
        {
            cin >> x >> y >> limit >> amount;
            graph[x][y] = graph[y][x] = {limit, amount};
        }
        auto pre = bfs(n, 1);
        cout << "Case #" << cnt++ << ": ";
        while (q--)
        {
            cin >> city >> weight;
            uint64_t cur = city, ans = 0;
            while (cur != 1)
            {
                uint64_t prev = pre[cur];
                auto [limit, amount] = graph[prev][cur];
                if (weight >= limit) ans = gcd(amount, ans);
                cur = prev;
            }
            cout << ans << " ";
        }
        cout << '\n';
    }
}

Segment Tree

官方题解使用了线段树 (Segment Tree) ,但我不会这个数据结构 😅 , TO BE DONE.

posted @   sinkinben  阅读(126)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示

📖目录