CF Round 783 Div2 题解

比赛链接

A题 Direction Change(思维,数学)

给定一个 \(n\)\(m\) 列的方格矩阵,要求我们从左上角走到右下角,可以上下左右移动(不可离开矩阵)。但是,不可以连续进行两个相同的操作(例如连续向下走两步)。

问,我们达到目的地的最短步数(不能达到则输出 -1)。

\(1\leq T\leq 10^3,1\leq n,m\leq 10^9\)

我们读入数据后不妨先 swap一下,确保 \(n\geq m\)

不难猜测:仅当 \(n\geq 3,m=1\) 时候无解,其他时候均存在一种方案。

我们考虑 \(m=2\) 的方案,就是蛇形走位。

\(m\geq 3\) 的时候,我们可以直接半斜着走到 \(m=2\) 的情况,然后按照上面的蛇形走位来即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
#define LL long long
LL f(LL n) {
    return 2 * n - (n % 2 == 0 ? 2 : 3);
}
LL solve()
{
    LL n, m;
    cin >> n >> m;
    if (n < m) swap(n, m);
    if (m == 1) {
        if (n == 1) return 0;
        else if (n == 2) return 1;
        else return -1;
    }
    if (m == 2) return f(n);
    LL d = m - 2;
    return 2 * d + f(n - d);
}

int main()
{
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

B题 Social Distance (数学,思维)

\(m\) 张椅子成环摆列(椅子下标从 \(0\)\(m-1\)),现在有 \(n\) 个人要坐在这些椅子上,但是由于疫情,第 \(i\) 个人左右的 \(a_i\) 张椅子必须全部是空着的(例如第 \(i\) 个人坐在位置 \(j\),那么椅子 \((j-a_i)\bmod m,(j-a_i+1)\bmod m,\cdots,(j+a_i)\bmod m\) 必须都是空着的)。问,能否成功将他们安排好?

\(2\leq n \leq 10^5,1\leq m,a_i\leq 10^9\)

一开始还就那个理解错了题意,WA 了 3发,麻了:相邻两个人并不要求他们的 id 也相同,换言之,这些人的坐的顺次是可以随便排列的。

那么,我们可以尝试看看,至少需要多少椅子:我们假设第 1 个人坐在椅子 0 上面,那么第二个人和第一个人至少相隔 \(\max(p_1,p_2)\),随后以此类推,最后还有 \(\max(p_n,p_1)\) 的空位。换言之,我们需要至少 \(n+\sum\limits_{i=1}^{n-1}\max(p_i,p_{i+1})+\max(p_n,p_1)\) 张椅子。

我们看看后面这个怎么排最大:我们对每个值算贡献,发现最大的数肯定要记两次,最小的数不被计入。随后,最优的方法莫过于每个数都计一次,也就是将数列从小到大排,然后得到值为 \(a_n+\sum\limits_{i=2}^{n}a_i\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 100010;
int n;
LL m, a[N], p[N];
bool solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    sort(a + 1, a + n + 1);
    LL sum = n + a[n];
    for (int i = 2; i <= n; ++i)
        sum += a[i];
    return sum <= m;
}
int main()
{
    int T;
    cin >> T;
    while (T--) puts(solve() ? "YES" : "NO");
    return 0;
}

C题 Make it Increasing (枚举,构造)

给定一个正整数数列 \(\{a_n\}\),现在要求我们用这个数列构造一个新数列 \(\{b_n\}\)

一开始,这个新数列的所有值均为 0,但我们可以进行若干次操作:

  1. \(b_i\) 加上 \(a_i\)
  2. \(b_i\) 减去 \(a_i\)

问至少需要多少次操作,可以使得 \(\{b_n\}\) 变为一个严格单调递增序列?

\(2\leq n\leq 5000,1\leq a_i\leq 10^9\)

如果我们假定让 \(b_1=0\),那么下面就好办了:对于每个数,我们直接贪心进行最小步骤,从前到后推一次即可。不过显然,这并不是最优的策略。

好在数据范围并不大,我们可以枚举 \(\{b_n\}\) 中到底是哪个元素最后保持为 0,然后分别左右扫一遍,并尝试更新答案即可,复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 5010;
int n;
LL a[N], s[N];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    LL ans = 1e18;
    for (int i = 1; i <= n; ++i) {
        memset(s, 0, sizeof(s));
        LL cnt = 0;
        for (int j = i - 1; j >= 1; --j) {
            LL k = s[j + 1] / a[j] + 1;
            s[j] = a[j] * k, cnt += k;
        }
        for (int j = i + 1; j <= n; ++j) {
            LL k = s[j - 1] / a[j] + 1;
            s[j] = a[j] * k, cnt += k;
        }
        ans = min(ans, cnt);
    }
    cout << ans << endl;
    return 0;
}

D题 Optimal Partition (DP,思维,权值线段树)

我们对于一个序列,可以计算其权值:若序列元素和为正,那么权值即为序列长度;若为负数,那么权值即为序列长度的负数;特别的,元素和为 0 时序列权值为 0。

给定一个数列 \(\{a_n\}\),我们可以将其划分为几个连续子序列,问怎么划分,可以使得子序列权值之和最大?

\(1\leq n\leq 5*10^5,-10^9\leq a_i\leq 10^9\)

我们考虑进行 DP,推出方程:

\[dp_i=\max\limits_{0\leq j<i} \{dp_j+f(j+1,i)\} \]

其中 \(dp_0=0\)\(f(l,r)\) 表示区间 \([l,r]\) 得到的子序列的权值(前缀和搞一遍之后就可以 \(O(1)\) 求了)

不过这个方程是一个 \(O(n^2)\) 的,所以我们需要进行优化。

我们考虑化简,得到:

\[dp_i=\max\limits_{0\leq j<i}(dp_j+\begin{cases}i-j&s_i>s_j\\ j-i&s_i<s_j \\ 0 &s_i=s_j\end{cases}) \]

考虑到递推是从前到后的,我们不禁想到了很多数据结构题的优化:

我们每次递推,都将该位置的信息推进数据结构内,然后当到达位置 \(i\) 的时候:

  1. 查询所有数据结构内 \(s\) 小于它的元素,然后求出 \(dp_j-j\) 的最大值
  2. 查询所有数据结构内 \(s\) 大于它的元素,然后求出 \(dp_j+j\) 的最大值
  3. 查询所有数据结构内 \(s\) 等于它的元素,然后求出 \(dp_j\) 的最大值

那么,我们开三棵权值线段树即可处理(考虑到权值范围,我们得离散化处理一下),总复杂度 \(O(n\log n)\)

(说起来简单,写起来头皮发麻,有很多小细节要处理,例如线段树所有位置初始值全部为极小,这样才可表示为“该位置无值”。

#include<bits/stdc++.h>
using namespace std;
const int N = 500010;
#define LL long long
struct SegmentTree {
    struct Node { int l, r, val; } a[N << 2];
    int mp[N];
    inline int ls(int x) { return x << 1; }
    inline int rs(int x) { return x << 1 | 1; }
    inline void pushup(int x) {
        a[x].val = max(a[ls(x)].val, a[rs(x)].val);
    }
    void build(int x, int l, int r) {
        a[x].l = l, a[x].r = r;
        if (l == r) {
            mp[l] = x, a[x].val = -1e9;
            return;
        }
        int mid = (l + r) >> 1;
        build(ls(x), l, mid);
        build(rs(x), mid + 1, r);
        pushup(x);
    }
    void change(int p, int val) {
        int x = mp[p];
        a[x].val = max(a[x].val, val);
        while (x >>= 1) pushup(x);
    }
    int query(int x, int l, int r) {
        if (l > r) return -1e9;
        if (l <= a[x].l && a[x].r <= r) return a[x].val;
        int mid = (a[x].l + a[x].r) >> 1, res = -1e9;
        if (l <= mid) res = max(res, query(ls(x), l, r));
        if (r >  mid) res = max(res, query(rs(x), l, r));
        return res;
    }
} st1, st2, st3;
int n, dp[N];
LL a[N];
void Discrete() {
    map<LL, int> mp;
    set<LL> s;
    for (int i = 0; i <= n; ++i) s.insert(a[i]);
    int cnt = 0;
    for (LL x : s) mp[x] = ++cnt;
    for (int i = 0; i <= n; ++i)
        a[i] = mp[a[i]];
}
int solve() {
    scanf("%d", &n);
    a[0] = 0;
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]), a[i] += a[i - 1];
    //init
    Discrete();
    st1.build(1, 1, n + 1), st1.change(a[0], 0);
    st2.build(1, 1, n + 1), st2.change(a[0], 0);
    st3.build(1, 1, n + 1), st3.change(a[0], 0);
    //solve
    for (int i = 1; i <= n; ++i) {
        //query
        int ans1 = st1.query(1, 1, a[i] - 1) + i;
        int ans2 = st2.query(1, a[i] + 1, n + 1) - i;
        int ans3 = st3.query(1, a[i], a[i]);
        dp[i] = max(ans1, max(ans2, ans3));
        //update
        st1.change(a[i], dp[i] - i);
        st2.change(a[i], dp[i] + i);
        st3.change(a[i], dp[i]);
    }
    return dp[n];
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) printf("%d\n", solve());
    return 0;
}
posted @ 2022-04-28 22:23  cyhforlight  阅读(15)  评论(0编辑  收藏  举报