「AGC058D」Yet Another ABC Problem

题目

点这里看题目。


给定非负整数 \(A,B,C\),你需要求出满足下列条件的字符串 \(S\) 的个数:

  1. \(S\)包含且仅包含恰好 \(A\)A\(B\)B\(C\)C

  2. \(S\) 中不包含子串 ABCBCACAB

答案对于 \(998244353\) 取模。

所有数据满足 \(0\le A,B,C\le 10^6\)

分析

教训:在没有尝试过常规做法之前,不要随便尝试骚的。


考虑普通的容斥。假设我们已枚举每个长度为 \(3\) 的子串是否为不合法字符串,现在计算方案数。

注意到,如果钦定的不合法子串之间有重叠,则它们之间是相容的,但是方案数会变少。具体来说,根据枚举的信息,我们可以将字符串划分成 \(k\) 段,每一段长度分别为 \(L_1,L_2,\dots,L_k\),不同段之间互不影响。则每一段的方案数仅仅由其第一个字符决定,所以总方案数为 \(3^k\)

假如只知道 \(L_1,L_2,\dots,L_k\),怎么计算贡献?这也就是要多考虑一个容斥系数。设 \(f_n\) 为一个长度为 \(n\) 的段的容斥系数(\(n\ge 3\)),则有递推关系:

\[f_n= \begin{cases} -f_{n-1}-f_{n-2}&n>3\\ -1&n=3 \end{cases} \]

最后结果是:

\[f_n= \begin{cases} -1&n\equiv 0\bmod 3\\ 1&n\equiv 1\bmod 3\\ 0&n\equiv 2\bmod 3 \end{cases} \]

此时我们已经得到了 \(O(m^2)\) 的 DP 做法了(设 \(m=A+B+C\))。


注意到,这个 DP 实际上就是把段按顺序组合起来(SEQ 构造),这个事 GF 也可以做。

因为三个字符数量都有限制,所以引入三元 OGF。我们将一个包含 \(a\)A\(b\)B\(c\)C 的字符串映射到 \(x^ay^bz^c\) 上。

\(F(x,y,z)\) 为单独一段的 OGF,而 \(G(x,y,z)\) 为答案的 OGF。根据组合对象可以直接得到:

\[\begin{aligned} F(x,y,z)&=\frac{-3xyz}{1-xyz}+\frac{x+y+z}{1-xyz}\\ G(x,y,z)&=\frac{1}{1-F(x,y,z)}\\ &=\frac{1}{1-\frac{x+y+z-3xyz}{1-xyz}}\\ &=\frac{1-xyz}{1-(x+y+z)+2xyz} \end{aligned} \]

Note.

按照一段的长度模 \(3\) 的余数进行分类。\(F\) 的第一项表示 \(n\bmod 3=0\) 的段,\(F\) 的第二项表示 \(n\bmod 3=1\) 的段。

怎么计算答案?换句话说,给定 \(a,b,c\),怎么计算 \(h(a,b,c)=[x^ay^bz^c]\frac{1}{1-(x+y+z)+2xyz}\)?我们直接展开

\[\begin{aligned} h(a,b,c) &=[x^ay^bz^c]\sum_{n\ge 0}[(x+y+z)-2xyz]^n\\ &=[x^ay^bz^c]\sum_{n\ge 0}\sum_{k=0}^n\binom{n}{k}(-2xyz)^k(x+y+z)^{n-k}\\ &=[x^ay^bz^c]\sum_{k\ge 0}(-2)^kx^ky^kz^k\sum_{n\ge k}\binom{n}{k}(x+y+z)^{n-k}\\ &=\sum_{0\le k\le \min\{a,b,c\}}(-2)^k[x^{a-k}y^{b-k}z^{c-k}]\sum_{n\ge 0}\binom{n+k}{k}(x+y+z)^n\\ &=\sum_{0\le k\le \min\{a,b,c\}}(-2)^k\binom{a+b+c-2k}{k}\binom{a+b+c-3k}{a-k,b-k,c-k} \end{aligned} \]

最后一步的解释:注意到 \((x+y+z)^n\) 展开的所有项都是 \(n\) 次的,所以包含 \(x^{a-k}y^{b-k}z^{c-k}\) 的项对应的 \(n\) 必然为 \(a+b+c-3k\)。另,\(\dbinom{a+b+c-3k}{a-k,b-k,c-k}\) 为多项式系数。

于是,这个计算可以在 \(O(A+B+C)\) 的时间内完成。

代码

#include <cstdio>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int mod = 998244353;
const int MAXN = 6e6 + 5;

template<typename _T>
inline void Read( _T &x ) {
	x = 0; char s = getchar(); bool f = false;
	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) Write( x / 10 );
	putchar( x % 10 + '0' );
}

int fac[MAXN], ifac[MAXN];

int A, B, C;

inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }

inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }

inline int Binom( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }

inline int Qkpow( int base, int indx ) {
	int ret = 1;
	while( indx ) {
		if( indx & 1 ) MulEq( ret, base );
		MulEq( base, base ), indx >>= 1;
	}
	return ret;
}

inline void Init( const int &n ) {
	fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
	ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
}

inline int Query( const int &a, const int &b, const int &c ) {
	if( a < 0 || b < 0 || c < 0 ) return 0;
	int ret = 0, pw = 1;
	for( int k = 0 ; k <= a && k <= b && k <= c ; k ++ ) {
		AddEq( ret, Mul( Mul( pw, Binom( a + b + c - 2 * k, k ) ),
				         Mul( fac[a + b + c - 3 * k], Mul( Mul( ifac[a - k], ifac[b - k] ), ifac[c - k] ) ) ) );
		MulEq( pw, mod - 2 );
	}
	return ret;
}

int main() {
	Read( A ), Read( B ), Read( C ), Init( 2 * ( A + B + C ) );
	Write( Sub( Query( A, B, C ), Query( A - 1, B - 1, C - 1 ) ) ), putchar( '\n' );
	return 0;
}

失败的脑洞

自动机

判断一个字符串是否合法可以用自动机。

很容易想到一个有 \(9\) 个结点的自动机(存储最后的两个字符)。进一步可以考虑将 AABA 合并、BBCB 合并、CCAC 合并,于是就只有 \(6\) 个结点了。最后注意到 BACBAC 本质上代表了两条长度为 \(2\) 的边,把字符串按照长度奇偶性分类后,每次转移两个字符,就可以压缩到 \(3\) 个结点。

关键在于,自动机仅仅关注了局部的限制,而全局字符数量限制是自动机本身无法解决的。如果想要满足这一点,我们可能只能在转移边中引入形式变元,但这会导致幂次变得非常难看。

唯一的想法是:设转移矩阵为 \(M(x,y,z)\),考虑 \(\sum_{n\ge 0}M(x,y,z)^n=\frac{I}{I-M(x,y,z)}\),这样可以将问题变成求逆。三阶求个逆应该还是没有问题吧?

不过我猜测将矩阵转成对角矩阵算幂和求逆本质上应该是类似的?

对称性

既然字符数量限制是全局的,那能不能用魔法打败魔法,用全局操作迎合全局限制?

具体地,在字符上作用 \(S_3\) 的变换,然后观察合法性的变化。结果是,有些不合法字符串是这种对称变换无法区分的。

周期性

这个就不是脑洞了。

我们可以很快地写出容斥系数 \(\{f_n\}\) 的 OGF \(F(x)\)

\[F(x)=\frac{-x^3}{1+x+x^2} \]

本来尝试的是对于分母直接分解,结果解出来两个复根 \(\frac{-1-\sqrt 3i}{2},\frac{-1+\sqrt 3i}{2}\)。定睛一看,居然还是 \(e^{\frac{2\pi}{3}i}\)\(e^{\frac{4\pi}{3}i}\),两个单位根。

马上注意到 \(F(x)=\frac{-x^3(1-x)}{1-x^3}\)。这在某种意义上解释了周期性。

posted @ 2023-01-30 21:19  crashed  阅读(52)  评论(0编辑  收藏  举报