“蔚来杯“2022牛客暑期多校训练营9 EI题解

E-Longest Increasing Subsequence

题目大意:
构造一个序列,要求序列中最长上升子序列的数量为n。

思路:
n可以用二进制表示成bk bk-1 …b1
对于bk,可以构造一个2 1 4 3 … 2*k 2*k-1这样一个序列,对于剩下的位bi,首先在序列2*i的位置后面插入一个大于2*k的数字,但是这样构造出来上升序列长度并不是k,因此还要在位置之后插入一个上升序列,这个上升序列的长度等于该位往上0的个数,选择合适的数,多个位置就能共用这个上升序列。
对于n=1、2、3的情况要进行特殊处理。

AC代码:

#include <bits/stdc++.h>
const int N = 1e2 + 5;
using namespace std;

vector<int> v[N], bits;

void solve()
{
    for (int i = 0; i < N; i++)
        v[i].clear();
    bits.clear();
    int n, m;
    cin >> n;
    m = n;
    for (int i = 0; m; i++)
    {
        if (m & (1 << i))
        {
            bits.push_back(i);
            m -= (1 << i);
        }
    }
    if (n == 1)
        cout << "2\n1 2\n";
    else if (n == 2)
        cout << "3\n2 1 3\n";
    else if (n == 3)
        cout << "4\n2 4 1 3\n";
    else
    {
        m = bits.back() * 2;
        for (int i = 1; i <= bits.back(); i++)
        {
            v[i].push_back(i * 2);
            v[i].push_back(i * 2 - 1);
        }
        for (int i = 0; i < bits.size() - 1; i++)
        {
            int len = bits[i + 1] - bits[i];
            while (len--)
                v[bits[i]].push_back(++m);
        }
        cout << m << "\n";
        for (int i = 0; i < N; i++)
            for (auto e : v[i])
                cout << e << " ";
        cout << "\n";
    }
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

I-The Great Wall II

题目大意:
有一个长度为n的数列a,将数列分成m段,使得每段中的最大值之和最小。求出m为1~n时的所有值。

前置知识:单调栈

思路:
可以用一个单调栈来维护状态,单调栈中的状态相当于将一个区间中相同的情况进行了压缩。
过程比较绕,因此下面的解释可能有误。

f[i][j]表示将前j个数字分成i段的最小值

性质1:当一个区间的左端点或右端点定下来时,扩大区间的长度,区间中的最大值不会减小,只会保持不变或增大。

对于f[i][j+1]。
情况1:如果a[j+1] <= a[j],那么把a[j+1]归到f[i][j]的最后一段中,结果不变。
比如1,2,3 2的分法和1,2,3的分法结果一样,也就是最后一个2不管加不加对结果都没有影响。
如果a[j+1]单独成为一段,那么f[i][j+1]的值就是f[i-1][j]+a[j+1]
因此当a[j+1] < a[j]时,f[i][j+1]= min(f[i][j],f[i-1][j]+a[j+1])

情况2:如果a[j+1] > a[j],将a[j]归并到f[i][j+1]的最后一段中,最后一段的值不会改变。
因此这时候就一直往前搜索,搜索的同时要保证前面剩余的个数要不少于i-1,因为每段至少要有一个数字。
直到不能再往前或是出现一个不小于a[j+1]的值为止。搜索的过程中记录每个数字合并到最后一段后其左侧的最小f值。
于是又回到了情况1。

AC代码:

#include <bits/stdc++.h>
const int inf = 1e9 + 7;
const int N = 8e3 + 5;
using namespace std;

struct node
{
    int a, val, m;
};
int f1[N], f2[N], a[N];

signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        f1[i] = max(f1[i - 1], a[i]); //分成一段的情况,就是找最大的a
    }
    cout << f1[n] << "\n";
    for (int i = 2; i <= n; i++) //将数列分成i段
    {
        stack<node> s;
        s.push({inf, inf, inf}); //防止栈中没有元素时访问出错
        for (int j = i; j <= n; j++)
        {
            int res = f1[j - 1];
            while (s.top().a < a[j])
            {
                res = min(res, s.top().val);
                s.pop();
            }
            s.push({a[j], res, min(s.top().m, res + a[j])});
            f2[j] = s.top().m;
        }
        for (int j = i; j <= n; j++)
            f1[j] = f2[j];
        cout << f2[n] << "\n";
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值