Codeforces Round 982 div2 个人题解(A~D2)

news2024/10/27 11:32:01

Codeforces Round 982 div2 个人题解(A~D2)

Dashboard - Codeforces Round 982 (Div. 2) - Codeforces

火车头

#define _CRT_SECURE_NO_WARNINGS 1

#include <algorithm>
#include <array>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>

#define ft first
#define sd second

#define yes cout << "yes\n"
#define no cout << "no\n"

#define Yes cout << "Yes\n"
#define No cout << "No\n"

#define YES cout << "YES\n"
#define NO cout << "NO\n"

#define pb push_back
#define eb emplace_back

#define all(x) x.begin(), x.end()
#define all1(x) x.begin() + 1, x.end()
#define unq_all(x) x.erase(unique(all(x)), x.end())
#define unq_all1(x) x.erase(unique(all1(x)), x.end())
#define sort_all(x) sort(all(x))
#define sort1_all(x) sort(all1(x))
#define reverse_all(x) reverse(all(x))
#define reverse1_all(x) reverse(all1(x))

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL

#define RED cout << "\033[91m"
#define GREEN cout << "\033[92m"
#define YELLOW cout << "\033[93m"
#define BLUE cout << "\033[94m"
#define MAGENTA cout << "\033[95m"
#define CYAN cout << "\033[96m"
#define RESET cout << "\033[0m"

// 红色
#define DEBUG1(x)                     \
    RED;                              \
    cout << #x << " : " << x << endl; \
    RESET;

// 绿色
#define DEBUG2(x)                     \
    GREEN;                            \
    cout << #x << " : " << x << endl; \
    RESET;

// 蓝色
#define DEBUG3(x)                     \
    BLUE;                             \
    cout << #x << " : " << x << endl; \
    RESET;

// 品红
#define DEBUG4(x)                     \
    MAGENTA;                          \
    cout << #x << " : " << x << endl; \
    RESET;

// 青色
#define DEBUG5(x)                     \
    CYAN;                             \
    cout << #x << " : " << x << endl; \
    RESET;

// 黄色
#define DEBUG6(x)                     \
    YELLOW;                           \
    cout << #x << " : " << x << endl; \
    RESET;

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
// typedef __int128_t i128;

typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pdd;
typedef pair<ll, int> pli;
typedef pair<string, string> pss;
typedef pair<string, int> psi;
typedef pair<string, ll> psl;

typedef tuple<int, int, int> ti3;
typedef tuple<ll, ll, ll> tl3;
typedef tuple<ld, ld, ld> tld3;

typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pll> vpll;
typedef vector<pli> vpli;
typedef vector<pss> vpss;
typedef vector<ti3> vti3;
typedef vector<tl3> vtl3;
typedef vector<tld3> vtld3;

typedef vector<vi> vvi;
typedef vector<vl> vvl;

typedef queue<int> qi;
typedef queue<ll> ql;
typedef queue<pii> qpii;
typedef queue<pll> qpll;
typedef queue<psi> qpsi;
typedef queue<psl> qpsl;

typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;
typedef priority_queue<string> pqs;
typedef priority_queue<pii> pqpii;
typedef priority_queue<psi> pqpsi;
typedef priority_queue<pll> pqpll;
typedef priority_queue<psi> pqpsl;

typedef map<int, int> mii;
typedef map<int, bool> mib;
typedef map<ll, ll> mll;
typedef map<ll, bool> mlb;
typedef map<char, int> mci;
typedef map<char, ll> mcl;
typedef map<char, bool> mcb;
typedef map<string, int> msi;
typedef map<string, ll> msl;
typedef map<int, bool> mib;

typedef unordered_map<int, int> umii;
typedef unordered_map<ll, ll> uml;
typedef unordered_map<char, int> umci;
typedef unordered_map<char, ll> umcl;
typedef unordered_map<string, int> umsi;
typedef unordered_map<string, ll> umsl;

std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());

template <typename T>
inline T read()
{
    T x = 0;
    int y = 1;
    char ch = getchar();
    while (ch > '9' || ch < '0')
    {
        if (ch == '-')
            y = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * y;
}

template <typename T>
inline void write(T x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x >= 10)
    {
        write(x / 10);
    }
    putchar(x % 10 + '0');
}

/*#####################################BEGIN#####################################*/
void solve()
{
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

A. Rectangle Arrangement

您正在为一个无限的正方形网格着色,其中所有单元格最初都是白色的。为此,您将获得 n n n 个印章。每个印章都是一个宽度为 w i w_i wi、高度为 h i h_i hi 的矩形。

您将使用每个印章恰好一次,将与网格上的印章大小相同的矩形涂成黑色。您不能旋转印章,并且对于每个单元格,印章必须完全覆盖它或根本不覆盖它。您可以在网格上的任何位置使用印章,即使印章区域覆盖的部分或全部单元格已经是黑色。

使用完所有印章后,您可以获得的黑色方块连接区域的周长的最小总和是多少?

输入
每个测试包含多个测试用例。第一行包含测试用例的数量 t t t ( 1 ≤ t ≤ 500 ) (1 \leq t \leq 500) (1t500)。测试用例的描述如下。

每个测试用例的第一行包含一个整数 n n n ( 1 ≤ n ≤ 100 ) (1 \leq n \leq 100) (1n100)

接下来的 n n n 行中的第 i i i 行包含两个整数 w i w_i wi h i h_i hi ( 1 ≤ w i , h i ≤ 100 ) (1 \leq w_i, h_i \leq 100) (1wi,hi100)

输出
对于每个测试用例,输出一个整数——使用完所有邮票后可以获得的黑色方块连通区域周长的最小和。

示例
输入

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

输出

20
8
10
400
16

提示
在第一个测试用例中,印章可以如左侧所示使用。每个印章用不同的颜色突出显示以便于识别。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在使用完这些印章后,形成一个黑色区域(如右侧所示),其周长为 20。可以证明,没有其他方式使用印章可以得到更低的总周长。

在第二个测试用例中,第二个和第三个印章可以完全放置在第一个印章内部,因此最小周长为 8。解题思路

解题思路

小学数学,观察样例发现总周长一定为$2\times (\text{max}(w)+\text{max}(h)) $

当然观察数据范围发现,也可以使用暴力模拟

代码实现

方法一

void solve()
{
    int n;
    cin >> n;
    int max_w = 0, max_h = 0;
    for (int i = 0; i < n; i++)
    {
        int w, h;
        cin >> w >> h;
        if (w > max_w)
            max_w = w;
        if (h > max_h)
            max_h = h;
    }
    cout << 2 * (max_w + max_h) << "\n";
}

方法二

赛时写的方法二,想了下好像会被hack掉,有可能超时,有改成方法一了,结果赛后再交没超时,白丢分

void solve()
{
    int n;
    cin >> n;
    vvi board(102, vi(102, 0));
    for (int i = 0; i < n; i++)
    {
        int x;
        int y;
        cin >> x >> y;
        // 把所有印章从左上角开始印
        for (int j = 1; j <= x; j++)
        {
            for (int k = 1; k <= y; k++)
            {
                board[j][k] = 1;
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= 100; i++)
    {
        for (int j = 1; j <= 100; j++)
        {
            if (board[i][j] == 1)
            {
                // 如果遇到一个方块盖了章,给答案加4,再减去和相邻方块也盖了章的数量
                ans += 4;
                ans -= board[i - 1][j] + board[i + 1][j] + board[i][j - 1] + board[i][j + 1];
            }
        }
    }
    cout << ans << endl;
}

B. Stalin Sort

斯大林排序是一种有趣的排序算法,旨在消除不合适的元素,而不是费心对它们进行正确排序,这使其具有 O ( n ) O(n) O(n) 的时间复杂度。

它的流程如下:从数组中的第二个元素开始,如果它严格小于前一个元素(忽略那些已经被删除的元素),则删除它。继续遍历数组,直到按非递减顺序排序。例如,数组 [ 1 , 4 , 2 , 3 , 6 , 5 , 5 , 7 , 7 ] [1,4,2,3,6,5,5,7,7] [1,4,2,3,6,5,5,7,7] 经过斯大林排序后变为 [ 1 , 4 , 6 , 7 , 7 ] [1,4,6,7,7] [1,4,6,7,7]

如果您可以通过对任何子数组重复应用斯大林排序,按非递增顺序对数组进行排序,则我们将数组定义为易受攻击的。

给定一个包含 n n n 个整数的数组 a a a,确定必须从数组中删除的最小整数数量,以使其易受攻击。

输入
每个测试由多个测试用例组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 500 ) (1 \leq t \leq 500) (1t500) — 测试用例的数量。后面是测试用例的描述。

每个测试用例的第一行包含一个整数 n n n ( 1 ≤ n ≤ 2000 ) (1 \leq n \leq 2000) (1n2000) — 数组的大小。

每个测试用例的第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an ( 1 ≤ a i ≤ 1 0 9 ) (1 \leq a_i \leq 10^9) (1ai109)

保证所有测试用例的 n n n 之和不超过 2000。

输出
对于每个测试用例,输出一个整数——必须从数组中删除的最小整数数量,以使其易受攻击。

示例
输入

6
7
3 6 4 9 2 5 2
5
5 4 4 2 2
8
2 2 4 4 6 6 10 10
1
1000
9
6 8 9 10 12 9 7 5 4
7
300000000 600000000 400000000 900000000 200000000 400000000 200000000

输出

2
0
6
0
4
2

提示
在第一个测试用例中,最优答案是删除数字 3 和 9。然后我们剩下 a = [ 6 , 4 , 2 , 5 , 2 ] a=[6,4,2,5,2] a=[6,4,2,5,2]。为了证明这个数组是易受攻击的,我们可以首先对子数组 [ 4 , 2 , 5 ] [4,2,5] [4,2,5] 应用斯大林排序,得到 a = [ 6 , 4 , 5 , 2 ] a=[6,4,5,2] a=[6,4,5,2],然后对子数组 [ 6 , 4 , 5 ] [6,4,5] [6,4,5] 应用斯大林排序,得到 a = [ 6 , 2 ] a=[6,2] a=[6,2],该数组是非递增的。

在第二个测试用例中,数组已经是非递增的,因此我们不需要删除任何整数。

解题思路

题目要求剩下的数组按照非递减排序,所以无论最后数组是什么情况,我们一定可以对它进行斯大林排序,使得它只剩一种数字。

因此,题目实际上可以转化成有两种操作

  • 花费1代价删除任意数字
  • 花费0代价删除 a j ( a j < a i , j > i ) a_j(a_j \lt a_i,j \gt i) aj(aj<ai,j>i)

所以我们可以枚举剩下的数字 a i a_i ai,它的花费 c o s t i = i − 1 + ∑ j = i + 1 n [ a j < a i ] cost_i=i-1+\sum_{j=i+1}^{n}[a_j\lt a_i] costi=i1+j=i+1n[aj<ai]

由于 n ≤ 2000 n\le2000 n2000,我们可以直接枚举统计 a j < a i a_j\lt a_i aj<ai的数量

代码实现

方法一

void solve()
{
    int n;
    cin >> n;
    vi a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    int ans = inf;
    for (int i = 1; i <= n; i++)
    {
        int cnt = 0;
        for (int j = i + 1; j <= n; j++)
        {
            if (a[j] > a[i])
                cnt++;
        }
        ans = min(ans, i - 1 + cnt);
    }
    cout << ans << endl;
}

方法二

对于 n ≤ 200000 n\le200000 n200000,方法一会超时,所有这时候我们可以倒着枚举 a i a_i ai,然后开一个对顶堆存储 a i a_i ai

void solve()
{
    int n;
    cin >> n;
    vi a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    int ans = inf;
    priority_queue<int, vi, greater<int>> ge;
    priority_queue<int> le;
    for (int i = n; i >= 1; i--)
    {
        while (!ge.empty() && ge.top() <= a[i])
        {
            le.push(ge.top());
            ge.pop();
        }
        while (!le.empty() && le.top() > a[i])
        {
            ge.push(le.top());
            le.pop();
        }
        ans = min(ans, int(i - 1 + ge.size()));
        ge.push(a[i]);
    }
    cout << ans << endl;
}

C. Add Zeros

给定一个数组 a a a,最初包含 n n n 个整数。在一次操作中,您必须执行以下操作:

选择一个位置 i i i,使得 1 < i ≤ ∣ a ∣ 1<i\leq |a| 1<ia a i = ∣ a ∣ + 1 − i a_i=|a|+1−i ai=a+1i,其中 ∣ a ∣ |a| a 是数组的当前大小。
i − 1 i−1 i1 个零附加到 a a a 的末尾。
在执行此操作多次后,数组 a a a 的最大可能长度是多少?

输入
每个测试包含多个测试用例。第一行包含测试用例的数量 t t t ( 1 ≤ t ≤ 1000 ) (1 \leq t \leq 1000) (1t1000)。测试用例的描述如下。

每个测试用例的第一行包含 n n n ( 1 ≤ n ≤ 3 ⋅ 1 0 5 ) (1 \leq n \leq 3 \cdot 10^5) (1n3105)——数组 a a a 的长度。

每个测试用例的第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an ( 1 ≤ a i ≤ 1 0 12 ) (1 \leq a_i \leq 10^{12}) (1ai1012)

保证所有测试用例的 n n n 之和不超过 3 ⋅ 1 0 5 3 \cdot 10^5 3105

输出
对于每个测试用例,输出一个整数——执行某些操作序列后可能的最大长度为 a a a

示例
输入

4
5
2 4 6 2 5
5
5 4 4 5 1
4
6 8 2 3
1
1

输出

10
11
10
1

提示
在第一个测试用例中,我们可以首先选择 i = 4 i=4 i=4,因为 a 4 = 5 + 1 − 4 = 2 a_4=5+1−4=2 a4=5+14=2。之后,数组变为 [ 2 , 4 , 6 , 2 , 5 , 0 , 0 , 0 ] [2,4,6,2,5,0,0,0] [2,4,6,2,5,0,0,0]。然后我们可以选择 i = 3 i=3 i=3,因为 a 3 = 8 + 1 − 3 = 6 a_3=8+1−3=6 a3=8+13=6。之后,数组变为 [ 2 , 4 , 6 , 2 , 5 , 0 , 0 , 0 , 0 , 0 ] [2,4,6,2,5,0,0,0,0,0] [2,4,6,2,5,0,0,0,0,0],其长度为 10。可以证明没有其他操作序列会使最终数组更长。

在第二个测试用例中,我们可以选择 i = 2 i=2 i=2,然后 i = 3 i=3 i=3,再然后 i = 4 i=4 i=4。最终数组将是 [ 5 , 4 , 4 , 5 , 1 , 0 , 0 , 0 , 0 , 0 , 0 ] [5,4,4,5,1,0,0,0,0,0,0] [5,4,4,5,1,0,0,0,0,0,0],长度为 11。

解题思路

我们把题目给的式子转化一下,变成 ∣ a ∣ = a i + i − 1 |a|=a_i+i-1 a=ai+i1

由于每个 a i a_i ai操作一次会使得 ∣ a ∣ |a| a增加 i − 1 i-1 i1,实际上就相当于每个 a i a_i ai都是对 a i + i − 1 a_i+i-1 ai+i1 a i + 2 i − 2 a_i+2i-2 ai+2i2建了一条边,点权为数组长度。

所以我们可以按照上面的规则进行建图,对这个图从 n n n进行 d f s dfs dfs d f s dfs dfs过程中维护一下遇到的最大点权,即为答案。

记得要加 v i s vis vis数组,不然会超时。

代码实现
void solve()
{
    ll n;
    cin >> n;
    vl a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    map<ll, vl> adj;
    for (ll i = 1; i <= n; i++)
    {
        adj[a[i] + i - 1].pb(a[i] + 2 * (i - 1));
    }
    ll ans = n;
    mlb vis;
    function<void(ll, ll)> dfs = [&](ll u, ll fa)
    {
        ans = max(ans, u);
        vis[u] = 1;
        for (auto &v : adj[u])
        {
            if (v == fa)
                continue;
            if (vis[v])
                continue;
            dfs(v, u);
        }
    };
    dfs(n, 0);
    cout << ans << endl;
}

D1. The Endspeaker (Easy Version)

这是该问题的简单版本。唯一的区别是,您只需要在此版本中输出最小的总操作成本。您必须解决两个版本才能破解。

您将获得一个长度为 n n n 的数组 a a a 和一个长度为 m m m 的数组 b b b(对于所有 1 ≤ i < m 1 \leq i < m 1i<m,其值为 b i > b i + 1 b_i > b_{i+1} bi>bi+1)。最初, k k k 的值为 1。您的目标是通过重复执行以下两个操作之一来使数组 a a a 为空:

类型 1 — 如果 k k k 的值小于 m m m 且数组 a a a 不为空,则可以将 k k k 的值增加 1。这不会产生任何成本。
类型 2 — 删除数组 a a a 的非空前缀,使其总和不超过 b k b_k bk。这会产生 m − k m−k mk 的成本。

您需要最小化操作的总成本,以使数组 a a a 为空。如果通过任何操作序列都无法做到这一点,则输出 − 1 −1 1。否则,输出操作的最小总成本。

输入
每个测试包含多个测试用例。第一行包含测试用例的数量 t t t ( 1 ≤ t ≤ 1000 ) (1 \leq t \leq 1000) (1t1000)。测试用例的描述如下。

每个测试用例的第一行包含两个整数 n n n m m m ( 1 ≤ n , m ≤ 3 ⋅ 1 0 5 , 1 ≤ n ⋅ m ≤ 3 ⋅ 1 0 5 ) (1 \leq n,m \leq 3 \cdot 10^5, 1 \leq n \cdot m \leq 3 \cdot 10^5) (1n,m3105,1nm3105)

第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an ( 1 ≤ a i ≤ 1 0 9 ) (1 \leq a_i \leq 10^9) (1ai109)

第三行包含 m m m 个整数 b 1 , b 2 , … , b m b_1,b_2,…,b_m b1,b2,,bm ( 1 ≤ b i ≤ 1 0 9 ) (1 \leq b_i \leq 10^9) (1bi109)

还保证所有 1 ≤ i < m 1 \leq i < m 1i<m 的结果是 b i > b i + 1 b_i > b_{i+1} bi>bi+1

保证所有测试用例的 n ⋅ m n \cdot m nm 之和不超过 3 ⋅ 1 0 5 3 \cdot 10^5 3105

输出
对于每个测试用例,如果有可能使 a a a 为空,则输出操作的最小总成本。如果没有可能的操作序列使 a a a 为空,则输出单个整数 − 1 −1 1

示例
输入

5
4 2
9 3 4 3
11 7
1 2
20
19 18
10 2
2 5 2 1 10 3 2 9 9 6
17 9
10 11
2 2 2 2 2 2 2 2 2 2
20 18 16 14 12 10 8 6 4 2 1
1 6
10
32 16 8 4 2 1

输出

1
-1
2
10
4

提示
在第一个测试用例中,最优操作序列的总成本为 1,过程如下:

  1. 执行类型 2 的操作,选择前缀为 [ 9 ] [9] [9],成本为 1。
  2. 执行类型 1 的操作, k k k 现在为 2,成本为 0。
  3. 执行类型 2 的操作,选择前缀为 [ 3 , 4 ] [3,4] [3,4],成本为 0。
  4. 执行类型 2 的操作,选择前缀为 [ 3 ] [3] [3],成本为 0。

数组 a a a 现在为空,所有操作的总成本为 1。

在第二个测试用例中,由于 a 1 > b 1 a_1 > b_1 a1>b1,无法删除任何前缀,因此数组 a a a 不能通过任何操作序列变为空。

解题思路

观察题目,发现是使用多种操作完成一个目标求最小代价,考虑dp

我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]为对于前 i i i个数字,使用第 j j j总操作时的最小代价。

易得状态转移方程
d p [ i ] [ j ] = min { d p [ p ] [ k ] } + m − j , sum [ p , i ] ≤ b [ j ] , 1 ≤ k ≤ j , 1 ≤ p ≤ i dp[i][j]= \text{min}\{dp[p][k]\}+m-j,\text{sum}[p,i]\le b[j],1\le k\le j,1\le p\le i dp[i][j]=min{dp[p][k]}+mj,sum[p,i]b[j],1kj,1pi
其中 sum [ p , i ] \text{sum}[p,i] sum[p,i]数组 a p ∼ a i a_p \sim a_i apai的区间和

根据状态转移方程写出递推过程

for (int i = 1; i <= n; i++)
{
    for (int j = 1; j <= m; j++)
    {
        int p = i;
        while (p >= 1 && pre[i] - pre[p - 1] <= b[j])
        {
            p--;
        }
        p++;
        for (int k = 1; k <= j; k++)
        {
            dp[i][j] = min(dp[i][j], dp[p - 1][k] + m - j);
        }
    }
}

发现时间复杂度为 O ( n m × max ( n , m ) ) O(nm\times \text{max}(n,m)) O(nm×max(n,m)),在 n n n m m m特别大的情况下肯定超时。

考虑优化。

优化一

对于 p p p的位置,我们可以使用二分优化枚举

function<int(int, ll)> bianrySearch = [&](int x, ll val) -> int
{
    int p = x;
    int l = 1, r = x;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        if (pre[x] - pre[mid - 1] <= val)
        {
            r = mid;
            p = mid;
        }
        else
        {
            l = mid + 1;
        }
    }
    return p;
};

for (int i = 1; i <= n; i++)
{
    for (int j = 1; j <= m; j++)
    {
        int l = 1, r = i;
        int p = bianrySearch(i, b[j]);
        for (int k = 1; k <= j; k++)
        {
            dp[i][j] = min(dp[i][j], dp[p - 1][k] + m - j);
        }
    }
}

时间复杂度降为 O ( n m 2 ) O(nm^2) O(nm2),在 m m m特别大的时候仍会超时

优化二

k k k的枚举,我们实际上是在查询区间 [ 1 , j ] [1,j] [1,j]的最小 d p [ i ] [ j ] dp[i][j] dp[i][j]值,所以我们可以使用树状数组存储 d p [ i ] [ j ] dp[i][j] dp[i][j]

树状数组实现

template <typename T>
struct Fenwick
{
    int n;
    vector<T> a;
    Fenwick(int n_ = 0) { init(n_); }
    void init(int n_) { n = n_, a.assign(n + 1, T{inf}); }
    void add(int x, const T &v)
    {
        for (int i = x; i <= n; i += i & -i)
            a[i] = min(a[i], v);
    }
    T query(int x)
    {
        T res{inf};
        for (int i = x; i > 0; i -= i & -i)
            res = min(res, a[i]);
        return res;
    }
};

递推过程

for (int i = 1; i <= n; i++)
{
    for (int j = 1; j <= m; j++)
    {
        if(b[j]<a[i])// 不能删除时跳过
            continue;
        int p = bianrySearch(i, b[j]);
        ll mn = tr[p - 1].query(j);// 使用树状数组查询,查询1<=k<=j中最小的dp[i][k]
        dp[i][j] = min(dp[i][j], mn + m - j);// 更新dp[i][j]
        tr[i].add(j, dp[i][j]);// 将dp[i][j]插入树状数组中,以便后续查询
    }
}

时间复杂度 O ( n m × max ( log ⁡ n , log ⁡ m ) ) O(nm\times \text{max}(\log n,\log m)) O(nm×max(logn,logm))

这时已经可以通过该题目了,但是还可以进行优化

优化三

考虑 j j j只能由 j − 1 j-1 j1转移过来,因此我们可以在最外层循环枚举 j j j,使用滚动数组优化掉树状数组

for (int j = 1; j <= m; j++)
{
    for (int i = 1; i <= n; i++)
    {
        if (b[j] < a[i])
            continue;
        int p = bianrySearch(i, b[j]);
        dp[i] = min(dp[i], dp[p - 1] + m - j);
    }
}

时间复杂度为 O ( n m log ⁡ n ) O(nm\log n) O(nmlogn)

优化四

观察发现,在求 p p p的过程中,我们将其转成一个大小为 b j b_j bj的窗口在数组 a a a上移动,其中左边界为 p p p,右边界为 i i i,这样我们就优化掉了二分

for (int j = 1; j <= m; j++)
{
    int l = 0, r = 1;
    while (r <= n)
    {
        while (l < r && pre[r] - pre[l] > b[j])
            l++;
        if (l < r)
            dp[r] = min(dp[r], dp[l] + m - j);
        r++;
    }
}

时间复杂度为 O ( n m ) O(nm) O(nm)

代码实现
void solve()
{
    int n, m;
    cin >> n >> m;
    vector<ll> a(n + 1);
    vector<ll> pre(n + 1);
    ll max_a = 0;

    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        max_a = max(max_a, a[i]);
        pre[i] = pre[i - 1] + a[i];
    }

    vl b(m + 1);
    for (int i = 1; i <= m; i++)
    {
        cin >> b[i];
    }
    if (max_a > b[1])
    {
        cout << "-1\n";
        return;
    }
    vector<ll> dp(n + 1, infll);
    dp[0] = 0;
    for (int j = 1; j <= m; j++)
    {
        int l = 0, r = 1;
        while (r <= n)
        {
            while (l < r && pre[r] - pre[l] > b[j])
                l++;
            if (l < r)
                dp[r] = min(dp[r], dp[l] + m - j);
            r++;
        }
    }
    cout << dp[n] << "\n";
}

D2. The Endspeaker (Hard Version)

这是该问题的难版本。唯一的区别是,您还需要在此版本中输出最佳序列的数量。

解题思路

顺着 D1 \text{D1} D1版本最后优化的思路,我们可以维护一个共享右边界的双重滑动窗口,滑动窗口大小为 b [ j ] b[j] b[j]

d p [ i ] dp[i] dp[i]为选择前 i i i个数字时的最小花费, c n t [ i ] cnt[i] cnt[i]为选择前 i i i个数字最小花费情况下的操作数量。

l l l为第一左边界, L L L为第二左边界, l l l负责维护 d p [ r ] dp[r] dp[r]从哪里转移过来, L L L负责 c n t [ r ] cnt[r] cnt[r]从哪里转移过来。

对于 d p [ r ] dp[r] dp[r]来说,转移方程和 D1 \text{D1} D1一致,对于 c n t [ r ] cnt[r] cnt[r]来说, c n t [ r ] = ∑ L = l R , d p [ R ] = d p [ l ] c n t [ L ] cnt[r]=\sum_{L=l}^{R,dp[R]=dp[l]}cnt[L] cnt[r]=L=lR,dp[R]=dp[l]cnt[L]

代码实现
const ll mod = 1e9 + 7;

void solve()
{
    int n, m;
    cin >> n >> m;
    vector<ll> a(n + 1);
    vector<ll> pre(n + 1);
    ll max_a = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        max_a = max(max_a, a[i]);
        pre[i] = pre[i - 1] + a[i];
    }
    vector<ll> b(m + 1);
    for (int i = 1; i <= m; i++)
    {
        cin >> b[i];
    }
    if (max_a > b[1])
    {
        cout << "-1\n";
        return;
    }

    vector<ll> dp(n + 1, inf);
    dp[0] = 0;
    vector<ll> cnt(n + 1, 0);
    cnt[0] = 1;
    for (int j = 1; j <= m; j++)
    {
        int l = 0, r = 1;
        int L = 0;
        ll sum = 0;
        while (r <= n)
        {
            // 窗口大小大于b[j],窗口内的序列都不可以用,要全部丢弃
            while (l < r && pre[r] - pre[l] > b[j])
            {
                sum = (sum + mod - cnt[l]) % mod;
                l++;
            }
            // 如果第二左边界小于了第一左边界,说明指向的右边界不同了,要将sum重置
            if (L < l)
            {
                L = l;
                sum = 0;
            }
            // 将共同左边界的操作序列数量全部记录
            while (L < r && dp[L] == dp[l])
            {
                sum = (sum + cnt[L]) % mod;
                L++;
            }
            if (l < r)
            {
                ll cost = dp[l] + m - j;
                if (cost < dp[r])
                // 如果花费更少,更新dp和cnt
                {
                    dp[r] = cost;
                    cnt[r] = sum;
                }
                else if (cost == dp[r]) // 花费相同,cnt加上sum
                {
                    cnt[r] = (cnt[r] + sum) % mod;
                }
            }
            r++;
        }
    }
    cout << dp[n] << " " << cnt[n] << "\n";
}

赛时把D1 dp的i和j写反了,找这个bug找了快一个小时,麻了,后面想到正解没时间写了,结束后三分钟写完了。太菜了,这几场掉大分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2224627.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件系统建设方案书(word参考模板)

1 引言 1.1 编写目的 1.2 项目概述 1.3 名词解释 2 项目背景 3 业务分析 3.1 业务需求 3.2 业务需求分析与解决思路 3.3 数据需求分析【可选】 4 项目建设总体规划【可选】 4.1 系统定位【可选】 4.2 系统建设规划 5 建设目标 5.1 总体目标 5.2 分阶段目标【可选】 5.2.1 业务目…

简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

API网关的作用--为什么微服务需要一个API网关?

微服务网关核心作用就是协议转换、安全隔离和流量控制 微服务架构中&#xff0c;API网关作为系统的入口点&#xff0c;可以统一处理所有客户端请求。 1&#xff09;协议转换&#xff1a;它能够支持多种通信协议&#xff08;如HTTP、gRPC等&#xff09;之间的相互转换&#xff…

【数据结构】快速排序(三种实现方式)

目录 一、基本思想 二、动图演示&#xff08;hoare版&#xff09; 三、思路分析&#xff08;图文&#xff09; 四、代码实现&#xff08;hoare版&#xff09; 五、易错提醒 六、相遇场景分析 6.1 ❥ 相遇位置一定比key要小的原因 6.2 ❥ 右边为key&#xff0c;左边先走 …

一个关于@JsonIgnore的isxxx()问题

一个关于JsonIgnore的问题 版本:2.13.5 <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><optional>true</optional></dependency>代码&#xff1a; Data public clas…

2024年10月27日历史上的今天大事件早读

公元前628年10月27日 春秋五霸之一晋文公重耳卒 1915年10月27日 美军进入一战前线 1921年10月27日 思想家严复逝世 1927年10月27日 中共创建井冈山根据地 1937年10月27日 八百壮士坚守四行仓库 1937年10月27日 伪“蒙古联盟自治政府”成立 1938年10月27日 日军对中国军队…

《向量数据库指南》——text-embedding-3-large与Mlivus Cloud打造语义搜索新纪元

使用text-embedding-3-large生成向量并将向量插入Mlivus Cloud实现高效语义搜索的深度解析与实战操作 在数字化时代,数据的处理和存储方式正在经历前所未有的变革。特别是随着大数据和人工智能技术的快速发展,向量数据库作为一种新型的数据存储和查询方式,正逐渐受到越来越…

KUKA机器人选定程序时提示“选择非法”的处理方法

KUKA机器人选定程序时提示“选择非法”的处理方法 如下图所示,选中某个程序,点击选定时, 系统提示:选择非法, 具体处理方法可参考以下内容: 选中该程序后,在右下角打开【编辑】菜单键,再选择【属性】,打开后可以看到程序的一般说明、信息模块和参数等信息,如下图所示…

AMD锐龙8845HS+780M核显 虚拟机安装macOS 15 Sequoia 15.0.1 (2024.10)

最近买了机械革命无界14X&#xff0c;CPU是8845HS&#xff0c;核显是780M&#xff0c;正好macOS 15也出了正式版&#xff0c;试试兼容性&#xff0c;安装过程和之前差不多&#xff0c;这次我从外网获得了8核和16核openCore&#xff0c;分享一下。 提前发一下ISO镜像地址和open…

基于SSH的物流运输货运车辆管理系统源码

基于经典的ssh&#xff08;Spring Spring MVC Hibernate&#xff09;框架与SaaS&#xff08;软件即服务&#xff09;模式&#xff0c;我们为运输企业与物流公司打造了一款开源且易用的车辆管理系统。 该系统主要包含以下核心模块&#xff1a; 档案管理 财务管理 借款管理 保…

研究生论文学习记录

文献检索 检索论文的网站 知网&#xff1a;找论文&#xff0c;寻找创新点paperswithcode &#xff1a;这个网站可以直接找到源代码 直接再谷歌学术搜索 格式&#xff1a;”期刊名称“ 关键词 在谷歌学术搜索特定期刊的关键词相关论文&#xff0c;可以使用以下几种方法&#…

【最全基础知识2】机器视觉系统硬件组成之工业相机镜头篇--51camera

机器视觉系统中,工业镜头作为必备的器件之一,须和工业相机搭配。工业镜头是机器视觉系统中不可或缺的重要组成部分,其质量和性能直接影响到整个系统的成像质量和检测精度。 目录 一、基本功能和作用 二、分类 1、按成像方式分 2、按焦距分 3、按接口类型分 4、按应用…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-22

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-22 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-22目录1. PoisonedRAG: Knowledge corruption attacks to retrieval-augmented generation of large language models摘要创新点…

【数据结构】双指针算法:理论与实战

双指针&#xff08;Two Pointers&#xff09;是一种经典的算法思想&#xff0c;广泛应用于数组、链表等数据结构的处理。该方法通过设置两个指针&#xff0c;在某种规则下移动指针来实现高效的计算与查找。这种算法相比传统的嵌套循环能显著优化时间复杂度&#xff0c;通常能够…

python读取学术论文PDF文件内容

目录 1、PyPDF22、pdfplumber3、PyMuPDF4、pdfminer总结 1、PyPDF2 PyPDF2 是一个常用的库&#xff0c;可以用来读取、合并、分割和修改PDF文件。读取pdf内容&#xff1a; import PyPDF2# 打开PDF文件 with open(ELLK-Net_An_Efficient_Lightweight_Large_Kernel_Network_for…

ThriveX 现代化博客管理系统

ThriveX 现代化博客管理系统 &#x1f389; &#x1f525; 首先最重要的事情放第一 开源不易&#xff0c;麻烦占用 10 秒钟的时间帮忙点个免费的 Star&#xff0c;再此万分感谢&#xff01; 下面开始进入主题↓↓↓ &#x1f308; 项目介绍&#xff1a; Thrive 是一个简而不…

行为设计模式 -责任链模式- JAVA

责任链设计模式 一 .简介二. 案例2.1 抽象处理者(Handler)角色2.2 具体处理者(ConcreteHandler)角色2.3 测试 三. 结论3.1 优缺点3.2 示例3.3 要点 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神…

xxl-job java.sql.SQLException: interrupt问题排查

近期生产环境固定凌晨报错&#xff0c;提示 ConnectionManager [Thread-23069] getWriteConnection db:***,pattern: error, jdbcUrl: jdbc:mysql://***:3306/***?connectTimeout3000&socketTimeout180000&autoReconnecttrue&zeroDateTimeBehaviorCONVERT_TO_NUL…

面试域——岗位职责以及工作流程

摘要 介绍互联网岗位的职责以及开发流程。在岗位职责方面&#xff0c;详细阐述了产品经理、前端开发工程师、后端开发工程师、测试工程师、运维工程师等的具体工作内容。产品经理负责需求收集、产品规划等&#xff1b;前端专注界面开发与交互&#xff1b;后端涉及系统架构与业…

本地缓存库分析(一):golang-lru

文章目录 本地缓存概览golang-lru标准lrulru的操作PutGet 2q&#xff1a;冷热分离lruPutGet expirable_lru&#xff1a;支持过期时间的lruPutGet过期 总结 本地缓存概览 在业务中&#xff0c;一般会将极高频访问的数据缓存到本地。以减少网络IO的开销&#xff0c;下游服务的压…