Loading

4.30 CW 模拟赛 T1. 序列

前言

先自己看, 不行借助题解
稍微有点心态

思路

首先计数问题, 考虑转化到线性上
尝试刻画一下最终的序列
有以下观察\((\)并不能简单做出\()\)

  • 一个完整段的前后分别是一些前缀和一些后缀
  • 后缀和前缀中间可以加上一个任意段

形象的来讲就是
pEbWg1K.png

不难发现用这样两个部分可以表示任意序列, 注意可能有位置不被覆盖
这时我们可以把问题转化为对这样序列的计数

考虑算重的问题, 不难发现我们只统计压着别的覆盖的覆盖或者对应位置与 \(a_i\) 不同的覆盖即可
由此, 状态可以分为 \(4\)

  • ): 上次覆盖到 \(i\), 以 \(< m\) 结尾
  • F] : 上次覆盖到 \(i\), 以 \(m\) 结尾, 且其已经压着别的覆盖, 或者它对应 \(a_i\) 的部分已经不同于 \(a_i\)
  • T] : 上次覆盖到 \(i\), 以 \(m\) 结尾, 且其仍未压着别的覆盖, 而且它对应 \(a_i\) 的部分与 \(a_i\) 相同
  • o: 上次没有覆盖到 \(i\), 使用 \(a_i\) 代替

注意到 ) 后面的覆盖一定是压着的
但是 ] 后面不一定

) -> [ )
     [ ](F)
o -> o
     [ ](根据 a[i] 决定 T/F)
     [ )(要求不同于 a[i])
F]-> o
     [ ](根据 a[i] 决定 T/F)
     ( ](根据 a[i] 决定 T/F)
     [ )(要求不同于 a[i])
     ( )(要求不同于 a[i])
T]-> o
     ( )(要求不同于 a[i])
     ( ](根据 a[i] 决定 T/F)

只给转移或许并不是很好理解
下面做出进一步的解释及推导

首先, 任意序列一定可以如下构造

\[\underbrace{[\hspace{1cm})[\hspace{0.6cm})[\hspace{1.2cm})}_{一些前缀(可以为空)} \underbrace{[\hspace{2cm}]}_{一个完整段(必须有)} \underbrace{(\hspace{0.6cm}](\hspace{1cm}](\hspace{0.4cm}]}_{一些后缀(可以为空)} \underbrace{(\hspace{0.8cm})}_{一个任意段} [\hspace{0.6cm})[\hspace{0.4cm})[\hspace{0.8cm})[\hspace{2cm}](\hspace{0.6cm}] \]

特别的

  • ] 之后可以放置 o 表示不覆盖
  • ] 之后放置的 ) 一定不会压着别的覆盖, 故要求与 \(a_i\) 不同
  • 放置 ( ) 时注意需要区分每种固定长度为 \(l\) 的连续段, 因此方案数为 \(\max(0, m - l - 1)\), 还需要注意如果存在和 \(a_i\) 相同的连续段, 则方案数减 \(1\)
  • 判断每种区间可以维护一个标签, 表示这个区间是否连续递增 \(1\), 然后判断一下起点终点即可知道这个区间的形式

注意到我们可以通过刻画 ), ], o 对应的后一个区间类型来表示一个区间

引入算重
注意到我们需要知道一个区间是否压着别的覆盖

因为我们是从左往右考虑的, 所以我们只需要知道这个区间的后一个区间是否压着别的覆盖即可
注意到 ) 后面的覆盖一定是压着的, 但是 ] 后面不一定
于是强行设 T] 表示当前他还没压着而且对应 \(a_i\) 的部分与 \(a_i\) 相同, 这一部分的方案数只有可能在他压着后面的区间的时候出现贡献
另外设 F] 表示当前他压着别的覆盖或者对应 \(a_i\) 的部分与 \(a_i\) 不同, 这一部分的方案数可以按照意义转移

给一份参考代码

参考代码 (from yukimianyan)
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
template <unsigned umod>
struct modint {/*{{{*/
  static constexpr int mod = umod;
  unsigned v;
  modint() = default;
  template <class T, enable_if_t<is_integral<T>::value, int> = 0>
    modint(const T& y) : v(y % mod + (is_signed<T>() && y < 0 ? mod : 0)) {}
  modint& operator+=(const modint& rhs) { v += rhs.v; if (v >= umod) v -= umod; return *this; }
  modint& operator-=(const modint& rhs) { v -= rhs.v; if (v >= umod) v += umod; return *this; }
  modint& operator*=(const modint& rhs) { v = (unsigned)(1ull * v * rhs.v % umod); return *this; }
  modint& operator/=(const modint& rhs) { assert(rhs.v); return *this *= qpow(rhs, mod - 2); }
  friend modint operator+(modint lhs, const modint& rhs) { return lhs += rhs; }
  friend modint operator-(modint lhs, const modint& rhs) { return lhs -= rhs; }
  friend modint operator*(modint lhs, const modint& rhs) { return lhs *= rhs; }
  friend modint operator/(modint lhs, const modint& rhs) { return lhs /= rhs; }
  template <class T> friend modint qpow(modint a, T b) {
    modint r = 1;
    for (assert(b >= 0); b; b >>= 1, a *= a) if (b & 1) r *= a;
    return r;
  }
  friend int raw(const modint& self) { return self.v; }
  friend ostream& operator<<(ostream& os, const modint& self) { return os << raw(self); }
  explicit operator bool() const { return v != 0; }
};/*}}}*/
using mint = modint<998244353>;
int n, m, a[1000010];
mint f[1000010][4];
int main() {
#ifndef LOCAL
#ifndef NF
  freopen("string.in", "r", stdin);
  freopen("string.out", "w", stdout);
#endif
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> n >> m;
  for (int i = 1; i <= n; i++) cin >> a[i];
  f[0][2] = 1;
  for (int i = 0; i < n; i++) {
    bool flag = true;
    f[i + 1][2] += f[i][2] + f[i][0];
    for (int j = 1; j <= min(m, n - i); j++) {
      if (j > 1) flag &= a[i + j] - 1 == a[i + j - 1];
      if (j == m) {
        f[i + j][0] += f[i][1];
        f[i + j][flag && a[i + j] == m ? 3 : 0] += f[i][2] + f[i][0];
      } else {
        f[i + j][1] += f[i][1];
        if (!flag || a[i + 1] != 1) f[i + j][1] += f[i][2] + f[i][0];
        f[i + j][flag && a[i + j] == m ? 3 : 0] += f[i][0] + f[i][3];
        f[i + j][1] += (f[i][3] + f[i][0]) * max(0, m - j - 1 - (flag && 1 < a[i + 1] && a[i + j] < m));
      }
    }
  }
  for (int i = 1; i <= n; i++) debug("f[%d] = {%d, %d, %d, %d}\n", i, raw(f[i][0]), raw(f[i][1]), raw(f[i][2]), raw(f[i][3]));
  cout << f[n][0] + f[n][2] << endl;
  return 0;
}

总结

线性计数多半是要找构造的

考虑这种给你一个不为 \(0\) 的原串让你做操作的, 往往需要找可以被原串替代的形式, 注意可能的误区

那个啥, 查重过高可能需要把原文放出来

posted @ 2025-05-05 10:26  Yorg  阅读(20)  评论(0)    收藏  举报