ARC059F Unhappy Hacking

ARC059F Unhappy Hacking

看到这里的题解都是二维 DP,我是从卡特兰数的方面考虑的此题。

如果这个题的模数换成 998244343 或是别的 NTT 模数,那么复杂度就可以通过分治 FFT 做到 O(nlog2n)。可惜换不得。但是貌似存在某神奇的多项式科技任意模数 NTT,那么理论上还是可以 O(nlog2n) 的。

但是本题解只对 O(n2) 的做法进行实现。

AtCoder 传送门

前置知识: 可以在这里 Day19 的 C 题笔记中找到卡特兰数的推导和变形,也可以查看 Wiki

简要题意

给一个 0/1 串 S,每次可以选择在末尾插入一个 01,或删除最后一个字符。求进行 n 次操作之后得到 S 的方案数。

无效退格

注意到一个比较头疼的事情是在没有字符的时候按退格,这样的退格不会删除任何东西,且一旦出现无效退格,一定不存在任何字符。(废话) 所以可以认为是一个新的子问题。

设操作 i 次,操作 i 是无效退格的方案数为 fi

为了求这个 f,我们需要计算从 0 个字符开始,不断进行操作,但是禁止无效退格。进行 2i 次操作的方案数为 gi。这样会打出 i 个字符,并且这 i 个字符会被 i 个有效退格删掉。这个问题等价于将 01 压入栈并且弹出。可以看出 gi 等于卡特兰数第 i 项乘 2i2i 枚举了每个插入的字符是 0 还是 1,卡特兰数第 1 项枚举了每个退格的时刻。

求出了 g,我们枚举倒数第二次无效空格的时刻 i12j,则 fi 可以表示为:

fi=j=0i12fi12jgj

这是卷积的形式,我们可以使得 g2i+1=gi,然后让 g 其余项为 0,那么式子就变成:

fi=j=1ifijgj

可以用分治 FFT 优化到 O(nlog2n),所以这个题理论上是可以 O(nlog2n) 的。有需要的人可以拿去出个加强版,把模数换成 998244353 然后把 n 开到 105 什么的。

我们枚举最后一个无效退格的时刻 x。那么相当于是在禁止无效退格的情况下用 nx 次操作得到目标串。所以我们枚举 x,对不同的 xnx 答案即可。

退格有效的情况

接下来考虑禁止无效退格,用 x 次操作凑出目标串的方案数。

S 的长度为 m,那么我们知道会有 x+m2 次插入操作,剩下的是删除操作。所以需要 xm 奇偶性相同。否则方案数为 0

我们先不管 x 次操作后的串是什么,求出只存在 0 的情况下,x 次操作后留下 m0 的方案数。显然是卡特兰数的变形,也就是 (xx+m2)(xx+m2+1)

最后我们考虑既有 0 也有 1 的情况。对于被删除了的 xm2 次插入,我们允许它们是任何数字,剩下的 m 个未被删除的插入,它们必须是目标位置的数字。因此只要在原来只有 0 的方案数的基础上乘以 2xm2 即可。

代码实现

这里没有对分治 FFT 进行任意模数 NTT 的实现。只写了暴力卷积:

const unsigned long long Mod(1000000007);
unsigned long long Fac[5005], Inv[5005], Two[5005], g[5005], f[5005], Ans(0);
unsigned m, n;
inline unsigned long long Inver(unsigned long long x) {
  unsigned long long Rt(1);
  unsigned y(1000000005);
  while (y) { if(y & 1) Rt = Rt * x % Mod; x = x * x % Mod, y >>= 1;}
  return Rt;
}
inline unsigned long long Solve(unsigned x) {
  unsigned Typ((x + m) >> 1);
  unsigned long long A(Two[Typ - m]), B(Inv[Typ] * Inv[x - Typ] % Mod);
  A = A * Fac[x] % Mod;
  B = B * A % Mod, Typ = x - Typ;
  if(Typ) --Typ, A = A * Inv[Typ] % Mod, A = A * Inv[x - Typ] % Mod;
  else A = 0;
  return Mod + B - A;
}
signed main() {
  n = RD(), f[0] = g[0] = Fac[0] = Two[0] = 1;
  while (getchar() >= '0') ++m;
  for (unsigned i(1); i <= n; ++i) Fac[i] = Fac[i - 1] * i % Mod;
  Inv[n] = Inver(Fac[n]);
  for (unsigned i(n); i; --i) Inv[i - 1] = Inv[i] * i % Mod;
  for (unsigned i(1); i <= n; ++i) Two[i] = (Two[i - 1] << 1), Two[i] -= (Two[i] >= Mod) ? Mod : 0;
  for (unsigned i(n >> 1); i; --i)
    g[i] = (((Fac[i << 1] * Inv[i + 1] % Mod) * Inv[i] % Mod) * Two[i]) % Mod;
  for (unsigned i(1); i <= n; ++i) for (unsigned j((i - 1) >> 1); ~j; --j)
    f[i] = (f[i] + f[i - 1 - (j << 1)] * g[j]) % Mod;
  for (unsigned i(n - m); ~i; --i) if(!(((n - i) ^ m) & 1))
    Ans = (Ans + f[i] * Solve(n - i)) % Mod;
  printf("%llu\n", Ans);
  return Wild_Donkey;
}
posted @   Wild_Donkey  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示