牛客刷题-Day3

牛客刷题-Day3

今日题目:\(1011-1015\)

1012 [NOIP1999]拦截导弹

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于 \(30000\) 的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入描述

\(1\) 行,若干个整数(个数 \(≤100000\))。

输出描述

\(2\) 行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

示例

输入

389 207 155 300 299 170 158 65

输出

6
2
解题思路

第一问是求解最长不上升子序列的长度,第二问是求解划分原序列为若干个不上升子序列的最小划分数。第一问的基础解法就是 \(O(n^2)\) 的时间复杂度,而第二问可以根据 \(Dilworth\) 定理将问题等价为求解原序列的最长上升子序列,也是 \(O(n^2)\) 的时间复杂度,如果不考虑任何优化。
如何优化?
首先考虑,\(f_i\) 表示长度为 \(i\) 的不上升子序列的最后一个值的最大值,那么有 \(f_{i+1}\le f_i\)

反证法:假设 \(v>u\),且 \(f_v\gt f_u\),那么在 \(v\) 的子序列中可以找到长度为 \(u\) 的不上升子序列,其最后的值 \(x>f_v>f_u\),那么与 \(f_u\) 为长度为 \(u\) 的不上升子序列的最后一个值的最大值,这一定义矛盾。

这样的话,\(f_i\) 这一序列是具有单调性的,可以考虑使用二分进行优化。
在基础解法中,我们考虑的是 \(dp_i=max\{dp_i,dp_j+1\},h_j\ge h_i\),这里的 \(dp_j\)\(dp_i\) 就是长度,设为 \(len=dp_j\)。如果 \(h_i\gt f_{len}\),由于 \(f_{len}\ge h_j\),则 \(h_i\gt h_j\),矛盾。因此总满足 \(h_i\le f_{len}\)。二分时要让 \(len\) 尽可能的大。

C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;

int n, h[N], f[N];

int main() {
    while (cin >> h[n]) {
        n++;
    }
    int len = 0;
    for (int i = 0; i < n; i++) {
        int l = 0, r = len;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (f[mid] >= h[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        f[r + 1] = h[i];
    }
    cout << len << endl;
    len = 0;
    for (int i = 0; i < n; i++) {
        int l = 0, r = len;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (f[mid] < h[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        f[r + 1] = h[i];
    }
    cout << len << endl;
    return 0;
}

1013 数学考试

题目描述

今天 \(qwb\) 要参加一个数学考试,这套试卷一共有 \(n\) 道题,每道题 \(qwb\) 能获得的分数为 \(a-i\)\(qwb\) 并不打算把这些题全做完,
他想选总共 \(2k\) 道题来做,并且期望他能获得的分数尽可能的大,他准备选 \(2\) 个不连续的长度为 \(k\) 的区间,
\([L,L+1,L+2,....,L+k-1]\)\([R,R+1,R+2,...,R+k-1]\)\(R >= L+k\))。

输入描述

第一行一个整数 \(T\)\(T<=10\)),代表有 \(T\) 组数据,
接下来一行两个整数 \(n,k,(2<=n<=200,000),(1<=k,2k <= n)\)
接下来一行 \(n\) 个整数 \(a_1,a_2,...,a_n,(-100,000<=a_i<=100,000)\)

输出描述

输出一个整数,\(qwb\) 能获得的最大分数。

示例

输入

2
6 3
1 1 1 1 1 1
8 2
-1 0 2 -1 -1 2 3 -1

输出

6
7
解题思路

因为涉及到区间和的问题,因此预先进行前缀和处理。

  • 状态表示:\(l_i\) 表示 \([1,i]\) 内区间长度为 \(k\) 的和的最大值,\(r_i\) 表示 \([i,n]\) 内区间长度为 \(k\) 的和的最大值。
  • 状态计算:\(l_i=max(l_{i-1}, a_i - a_{i - k})\),看区间是否包括 \(a_i\),如果不包括必然是 \(l_{i-1}\),否则的话为 \(a_i - a_{i - k}\),因为是长度为 \(k\) 的连续区间;\(r_i\) 同理。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
typedef long long LL;

int T, n, k;
LL a[N], l[N], r[N];

int main() {
    scanf("%d", &T);
    while (T--) {
        memset(l, 128, sizeof l);
        memset(r, 128, sizeof r);
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; i++)
            scanf("%lld", &a[i]);
        for (int i = 1; i <= n; i++)
            a[i] += a[i - 1];
        for (int i = k; i <= n - k; i++) // l[i] 表示以 i 为右边界的长度为 k 区间的值
            l[i] = max(l[i - 1], a[i] - a[i - k]);
        for (int i = n - k + 1; i > k; i--) // r[i] 表示以 i 为左边界的长度为 k 区间的值
            r[i] = max(r[i + 1], a[i + k - 1] - a[i - 1]);
        LL ans = -1e18;
        for (int i = k; i <= n - k; i++)
            ans = max(ans, l[i] + r[i + 1]);
        printf("%lld\n", ans);
    }
    return 0;
}

1015 购物

题目描述

在遥远的东方,有一家糖果专卖店。
这家糖果店将会在每天出售一些糖果,它每天都会生产出 \(m\) 个糖果,第 \(i\) 天的第 \(j\) 个糖果价格为 \(C[i][j]\) 元。
现在的你想要在接下来的 \(n\) 天去糖果店进行选购,你每天可以买多个糖果,也可以选择不买糖果,但是最多买 \(m\) 个。(因为最多只生产 \(m\) 个)买来糖果以后,你可以选择吃掉糖果或者留着之后再吃。糖果不会过期,你需要保证这 \(n\) 天中每天你都能吃到至少一个糖果
这家店的老板看你经常去光顾这家店,感到非常生气。(因为他不能好好睡觉了)于是他会额外的要求你支付点钱。具体来说,你在某一天购买了 \(k\) 个糖果,那么你在这一天需要额外支付 \(k^2\) 的费用。
那么问题来了,你最少需要多少钱才能达成自己的目的呢?

输入描述

第一行两个正整数 \(n\)\(m\),分别表示天数以及糖果店每天生产的糖果数量。
接下来 \(n\) 行(第 \(2\) 行到第 \(n+1\) 行),每行 \(m\) 个正整数,第 \(x+1\) 行的第 \(y\) 个正整数表示第 \(x\) 天的第 \(y\) 个糖果的费用。

输出描述

输出只有一个正整数,表示你需要支付的最小费用。

示例1

输入

3 2
1 1
100 100
10000 10000

输出

107
示例2

输入

5 5
1 2 3 4 5
2 3 4 5 1 
3 4 5 1 2 
4 5 1 2 3 
5 1 2 3 4

输出

10
备注

对于 \(100\%\) 的数据,\(1 ≤ n, m ≤ 300\),所有输入的数均 \(≤ 10^6\)

解题思路

首先题目要求每天至少有一颗糖果吃,那么每天至少需要购买一颗糖果,那么要花费最少,每天购买的糖果必然是价格最小的,这里我们对每天的糖果价格进行排序。

  • 状态表示:\(f_{i,j}\) 表示前 \(i\) 天购买 \(j\) 颗糖果的费用,需要求解其最小值。
  • 状态计算:第 \(i\) 天购买 \(k\) 颗糖果的费用为 \(sum_{i,k}+k^2\)\(sum_{i,k}\) 为第 \(i\) 天前 \(k\) 颗糖果的前缀和。那么 \(f_{i,j}=min(f_{i,j},f_{i-1,j-k}+sum_{i,k}+k^2)\)\(j-k\ge i-1\)
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 310;

int n, m;
LL c[N][N], f[N][N], sum[N][N]; // f[i][j] 表示第 i 天买了 j 颗糖果的费用

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%lld", &c[i][j]);
    memset(f, 127, sizeof f);
    for (int i = 1; i <= n; i++) {
        sort(c[i] + 1, c[i] + m + 1);
        for (int j = 1; j <= m; j++) // 每天要买就买前 i 个
            sum[i][j] = sum[i][j - 1] + c[i][j];
    }
    f[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = i; j <= n; j++) { // 第 i 天至少要买到 i 个糖果
            f[i][j] = f[i - 1][j];
            for (int k = 1; k <= m; k++) {
                if (j >= k && j - k >= i - 1)
                    f[i][j] = min(f[i][j], f[i - 1][j - k] + sum[i][k] + k * k);
            }
        }
    }
    cout << f[n][n] << endl;
    return 0;
}
posted @ 2025-09-23 16:00  Cocoicobird  阅读(22)  评论(0)    收藏  举报