“蔚来杯“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 ,上升时可以上升到任意层并随时下降,但是下降要一直下降到一层才能再上升。电梯每秒运行一层,换方向和上下人不占用时间,问电梯最短运行时间。

题解

代码

未完

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值