ARC178 补

C Sum of Abs 2

给定一个长度为 \(n\) 的序列 \(a\),对于每个 \(a_i\),问在满足条件的序列 \(b\) 中,最小的序列最大值。

条件:长度为 \(l\)\(\sum\limits_{j=1}^{L-1}\sum\limits_{k=j+1}^L|b_j-b_k| = a_i\)

\(a_i,n,l \leq 2e5\)

Solution

序列 \(a\) 是摆设,本质上是多组询问。可以全部预处理了再回答答案。

既然是两两数之差的和,那我不如将 \(b\) 排序,把绝对值拆了,答案也成了 \(b_l\)

没有了绝对值,就可以直接简单计数一下每个 \(b\) 出现了多少次,便得到了式子:

\(a_i = \sum\limits_{j=1}^{L-1}j(l-j)(b_{j+1}-b_j)\)

相邻两数之差,不如直接差分 \(c_i = b_{i+1}-b_i\)。不仅没了差式,还把答案化为了序列之和。

wow 好像是背包。容量为 \(a_i\),第 \(i\) 个物品重量为 \(j(l-j)\),代价为 \(1\),求最小代价。

是不是就做完了。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2e5 + 1, kI = 1e9;

int n, l;
int a[kN], f[kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> l;
  fill_n(f, kN, kI), f[0] = 0;
  for (int i = 1, c; i * (l - i) < kN && 2 * i <= l; i++) {
    c = i * (l - i);
    for (int j = c; j < kN; j++) {
      f[j] = min(f[j], f[j - c] + 1);
    }
  }
  for (int i = 1, x; i <= n; i++) {
    cin >> x;
    cout << (f[x] == kI ? -1 : f[x]) << '\n';
  }
  return 0;
}

D Delete Range Mex

给定一个长度为 \(m\),值域 \([0, n)\) 的序列 \(a\)。问有多少个满足条件的排列 \(p\)

条件:可以通过不断删除区间 Mex 使自己变成序列 \(a\)

Solution

因为是删除 Mex,所以如果你删除了一个数 \(x\),则大于 \(x\) 的数你再也无法删除,因此只能把 \(a\) 里没有的数从大到小删。

假如你要删除序列中的一个数 \(x\),则 \([0,x)\) 的数在序列中必须全部在 \(x\) 左右其中一侧。不然要包括所有的 \([0,x)\) 的数,这个区间就必须跨过 \(x\),其 Mex 也不可能是 \(x\)

所以我们可以考虑向 \(a\) 里插入数。我们只需要保证插入的数永远在比他小的数的同一侧就行了。

但是不好实现对吧,所以我们考虑向一个空序列从 \(0\)\(n-1\) 插入数,这样遇到要删除的数就可以直接把它放在序列的最左边或者最右边就行了。

设计 dp 状态。用两个维度来描述当前序列在最终排列中的位置。设 \(f_{i,l,r}\) 表示现在这个序列最大值为 \(i\),且在这个序列左右分别还有 \(l,r\) 个数要填。中间可以暂时空缺,不用填数。

初始化需要考虑 \(0\) 是否存在于 \(a\)

现在我考虑到数字 \(i\),如果我要将其插入,需要特判两种情况。

  • \(i\) 在序列 \(a\) 中。

好耶不用删掉,那我直接令 \(k\) 为插入的位置,\(f_{i,\min(l,k-1),\min(r,m-k)} \Leftarrow f_{i-1,l,r}\)

因为 \(i\)\(a\) 中,所以插入的位置 \(k\) 是确定的,可以利用 \(a\) 中的信息更新答案。

  • \(i\) 不在序列 \(a\) 中。

删掉 \(i\),那我直接塞一边不就行了。\(f_{i,l,r}=\sum\limits_{l'=l}^m f_{i-1,l',r}+\sum\limits_{r'=r}^m f_{i-1,l,r'}\)

好像是 \(O(m^3)\) 了怎么办。你发现又是只动一边,是不是可以前缀和优化。

很大一部分是借鉴官方题解,因为当时不会看了题解,读者大可以理解为我在做官题翻译。

\(\\\)

Code
// LUOGU_RID: 159742013
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <atcoder/modint>
#include <cstring>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using namespace atcoder;
using LL = long long;
using mint = modint998244353;
using PII = pair<int, int>;
constexpr int kN = 500 + 1;

int n, m, p[kN];
mint f[kN][kN], ls[kN][kN], rs[kN][kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  fill_n(p, n, -1);
  for (int i = 0, x; i < m; i++) {
    cin >> x, p[x] = i;
  }
  if (p[0] == -1) {
    for (int i = 0; i <= m; i++) {
      f[i][m - i] = 1;
    }
  } else {
    f[p[0]][m - 1 - p[0]] = 1;
  }
  for (int i = 1; i < n; i++) {
    memcpy(ls[0], f[0], sizeof(f));
    memset(f[0], 0, sizeof(f));
    if (p[i] == -1) {
      memcpy(rs[0], ls[0], sizeof(f));
      for (int l = m; l >= 0; l--) {
        for (int r = m - l; r >= 0; r--) {
          l && (ls[l - 1][r] += ls[l][r], 0);
          r && (rs[l][r - 1] += rs[l][r], 0);
          f[l][r] = ls[l][r] + rs[l][r];
        }
      }
    } else {
      for (int l = 0; l <= m; l++) {
        for (int r = 0; l + r <= m; r++) {
          f[min(l, p[i])][min(r, m - p[i] - 1)] += ls[l][r];
        }
      }
    }
  }
  cout << f[0][0].val() << '\n';
  return 0;
}
posted @ 2024-07-14 22:16  Lightwhite  阅读(21)  评论(0)    收藏  举报