“蔚来杯“2022牛客暑期多校训练营2

本文介绍了三道算法竞赛题目,涉及序列构造以最小化最长上升和下降子序列的权值、等差数列的最小代价修改以及合法括号序列的生成。解析了线性回归、动态规划等解题策略,并给出了详细的代码实现。

"蔚来杯"2022牛客暑期多校训练营2

G Link with Monotonic Subsequence

题目大意

构造一个排列,使其 max(lis§, lds§) 最小,其中lis是最长上升子序列长度,lds是最长下降子序列长度。

题解

结论:排列权值的最小值为 ⌈√n⌉。

可以把n个数字分成⌈√n⌉组,如果n = 9,则为789、456、123,显然每组里面递增,各个组之间递减。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int n, t;
int a[maxn];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n;
        int num = ceil(sqrt(n));
        int head = num * num - (num - 1);
        int cnt = 0;
        for (int i = 0; i < num; i++)
        {
            for (int j = 0; j < num; j++)
            {
                if (head + j > n)
                    continue;
                a[cnt++] = head + j;
            }
            head -= num;
        }
        cout << a[0];
        for (int i = 1; i < cnt; i++)
            cout << " " << a[i];
        cout << endl;
    }
}

J Link with Arithmetic Progression

题目大意

有一个数列 a ,将其修改为一个等差数列 b,代价为 ∑ (ai − bi)2,求最小代价。

题解

该题使用线性回归,直接套式子即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
typedef long double ld;
int t, n;
ld a[maxn];
ld sx, sy, sxx, sxy, A, B;
double res;
namespace GTI
{
    char gc(void)
    {
        const int S = 1 << 16;
        static char buf[S], *s = buf, *t = buf;
        if (s == t)
            t = buf + fread(s = buf, 1, S, stdin);
        if (s == t)
            return EOF;
        return *s++;
    }
    int gti(void)
    {
        int a = 0, b = 1, c = gc();
        for (; !isdigit(c); c = gc())
            b ^= (c == '-');
        for (; isdigit(c); c = gc())
            a = a * 10 + c - '0';
        return b ? a : -a;
    }
}
using GTI::gti;
int main()
{
    t = gti();
    while (t--)
    {
        sx = sy = sxx = sxy = B = A = res = 0;
        n = gti();
        for (int i = 1; i <= n; i++)
        {
            a[i] = gti();
            sy += a[i];
            sx += (ld)i;
            sxy += (ld)i * a[i];
            sxx += (ld)i * (ld)i;
        }
        B = ((ld)n * sxy - sx * sy) / ((ld)n * sxx - sx * sx);
        A = (sy * sxx - sx * sxy) / (n * sxx - sx * sx);
        for (int i = 1; i <= n; i++)
            res += (a[i] - A - B * (ld)i) * (a[i] - B * (ld)i - A);
        cout << fixed << setprecision(15) << res << endl;
    }
}

K Link with Bracket Sequence I

题目大意

已知括号序列 a 长度为n,且 a 是一个长度为 m 的合法括号序列 b 的子序列,求可能的序列 b 的数量。

题解

该题使用dp,记 dp(i,j,k)表示在序列 b 的前 i 位中,与 a 的 lcs 为 j ,且左括号比右括号多 k 个的方案数。

初始化:dp [0] [0] [0]=1;dp [i] [0] [j] += (j - 1 < 0 ? 0 : dp [i - 1] [0] [j - 1]) + dp [i - 1] [0] [j + 1]。

递推公式:每次枚举b中第i个字符的可能情况,以及其是否与序列a的第j个字符匹配,所以一共有四种情况,可以根据a[j]的字符分为两类。

当a[j]是左括号时,

①b[i]是左括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp[i - 1] [j - 1] [k - 1]) % mod;

②b[i]是右括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp [i - 1] [j] [k + 1]) % mod;

当a[j]是右括号时,

①b[i]是右括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp [i - 1] [j - 1] [k + 1]) % mod;

②b[i]是左括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp [i - 1] [j] [k - 1]) % mod;

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e2 + 5;
const int mod = 1e9 + 7;
int t, n, m;
char a[maxn];
int dp[maxn][maxn][maxn];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        memset(dp, 0, sizeof dp);
        cin >> n >> m >> a + 1;
        dp[0][0][0] = 1;
        for (int i = 1; i <= m; i++)
        {
            for (int j = 0; j <= i; j++)
            {
                dp[i][0][j] += (j - 1 < 0 ? 0 : dp[i - 1][0][j - 1]) + dp[i - 1][0][j + 1];
                dp[i][0][j] %= mod;
            }
        }
        for (int i = 1; i <= m; i++)
        {
            for (int j = 1; j <= min(i, n); j++)
            {
                for (int k = 0; k <= i; k++)
                {
                    if (a[j] == '(')
                    {
                        dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k - 1]) % mod;
                        dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k + 1]) % mod;
                    }
                    if (a[j] == ')')
                    {
                        dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k + 1]) % mod;
                        dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k - 1]) % mod;
                    }
                }
            }
        }
        cout << dp[m][n][0] << endl;
    }
    return 0;
}

D Link with Game Glitch

题目大意

给定 m 个物品合成的方式,即用 ai 个 bi 物品可以合成 ci 个 di 物品,求一个最大的合成损耗参数 w ,使得所有物品都无法通过无限合成的方式无限获得。

题解

建图,对于每个物品建点,每个合成方式由 bi 向 di 建有向边,边权为 ci/ai

该题实际上是要求一个最大的 w ,使得在每条边的边权乘上 w 之后,不存在一个乘积大于 1 的环。

可以二分答案,check 的问题类似于求负环。另外,由于边权乘积较大,需要对其取对数。

取对数处理:假如某个环的边权为a、b、c、d,那么要保证abcd≤1,两边取对数,log a + log b + log c + log d ≤ 0,即- log a - log b - log c - log d ≥ 0。

所以如果建图时存入的边权为-log ci/ai,那么即为判断是否存在负环。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
const double eps = 1e-10;
int n, m, ans;
double dis[maxn];
int cnt[maxn];
bool vis[maxn];
struct Edge
{
    int v;
    double w;
    Edge(int v = 0, double w = 0) : v(v), w(w) {}
};
vector<Edge> e[maxn];
bool checked(double mid)
{
    memset(dis, 0, sizeof dis);
    memset(vis, 0, sizeof vis);
    memset(cnt, 0, sizeof cnt);
    queue<int> q;
    for (int i = 1; i <= n; i++)
        q.push(i), vis[i] = 1;
    while (!q.empty())
    {
        int now = q.front();
        q.pop();
        vis[now] = 0;
        for (int i = 0; i < e[now].size(); i++)
        {
            int v = e[now][i].v;
            double w = e[now][i].w;
            if (dis[v] > dis[now] + w + mid)
            {
                dis[v] = dis[now] + w + mid;
                if (++cnt[v] == n + 5)
                    return 0;
                if (!vis[v])
                {
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    return 1;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        int b, d;
        double a, c;
        cin >> a >> b >> c >> d;
        e[b].emplace_back(d, log(a) - log(c));
    }
    double l = 0, r = 1, mid;
    while (abs(r - l) >= eps)
    {
        mid = (l + r) / 2;
        ans++;
        if (checked(-log(mid)))
            l = mid;
        else
            r = mid;
    }
    cout << fixed << setprecision(10) << r << endl;
    return 0;
}

H Take the Elevator

题目大意

n 个人坐电梯,楼高 m ,每个人有起始楼层和目标楼层。电梯有载客量限制 k ,上升时可以上升到任意层并随时下降,但是下降要一直下降到一层才能再上升。电梯每秒运行一层,换方向和上下人不占用时间,问电梯最短运行时间。

题解

代码

未完

### 暑期训练营的适合水平分析 暑期训练营是一项面向算法竞赛爱好者的系列比赛,主要目的是为选手提供一个练习和提升的机会[^1]。该训练营通常吸引了来自全国各地的编程爱好者以及准备参加 ACM-ICPC 或其他算法竞赛的学生参与。根据以往的经验,以下是对适合水平的详细分析: #### 1. **基础要求** 训练营中的题目难度跨度较大,从入门级到高难度均有覆盖。对于新手选手来说,如果具备一定的算法基础(如掌握基本的数据结构、排序算法、搜索算法等),可以尝试参与并从中学习[^2]。 #### 2. **中级选手** 中级水平的选手通常已经熟练掌握了常见的算法模板,例如动态规划、图论(最短路径、最小生成树等)、字符串匹配等。这类选手可以通过训练营中的中等难度题目进一步巩固知识,并挑战更高难度的问题以提升能力[^3]。 #### 3. **高级选手** 高级水平的选手通常是 ACM-ICPC 区域赛或更高级别比赛的参赛者。他们能够快速解决大部分常规问题,并专注于研究复杂算法和优化技巧。对于这些选手,训练营是一个检验自身实力、发现不足的好机会[^4]。 #### 4. **团队协作能力** 值得注意的是,训练营不仅考察个人能力,还强调团队合作的重要性。许题目需要名队员分工合作才能高效完成。因此,即使是高水平的个人选手,也需要通过训练营来磨练与队友的配合能力[^5]。 ```python # 示例代码:计算最短路径(Dijkstra算法) import heapq def dijkstra(graph, start): n = len(graph) dist = [float('inf')] * n dist[start] = 0 heap = [(0, start)] while heap: d, u = heapq.heappop(heap) if d > dist[u]: continue for v, w in graph[u]: if dist[u] + w < dist[v]: dist[v] = dist[u] + w heapq.heappush(heap, (dist[v], v)) return dist ``` 上述代码展示了图论中经典的 Dijkstra 算法实现,这是训练营中可能出现的基础知识点之一。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值