[AGC058D]Yet Another ABC String

这篇博客探讨了一种处理ABC字符串的优化动态规划(DP)方法。作者首先介绍了问题的背景,指出当有连续不合法部分时,可以将其转化为一段连续的ABC模式。接着,详细阐述了DP状态转移方程的建立,通过生成函数优化复杂度,最终给出一个时间复杂度为O(A+B+C)的解决方案。文章中还包含了源代码实现。
摘要由CSDN通过智能技术生成

Yet Another ABC String

题解

首先看到这个题,一个方向是去考虑容斥。
可以发现,如果有连续不合法部分的话,可以发现,他一定是一段连续的 . . . A B C A B C A B C A B C . . . ...ABCABCABCABC... ...ABCABCABCABC...
这是一个非常好的性质,因为如果我们之间容斥不合法位置的起点集合的话是非常麻烦的,这可以让我们得到一个非常好的转化。
每次强制原串中的一些部分呈现我们上面所说的这样的连续段,容斥我们这些连续段的集合。

考虑选择一个这样长度为 n n n的段,它所贡献容斥系数。
手推一下前几项可以发现,容斥系数在模 3 3 3 0 0 0的位置上为 − 1 -1 1,在模 3 3 3 1 1 1的位置上为 1 1 1,模 3 3 3 2 2 2的位置上为 0 0 0
证明大概可以考虑通过递推式归纳证明,不是很会证明。(先留个坑)
好的,既然知道这东西了,我们也就很容易设计出来一个 d p dp dp转移。
由于有效的要么是模 3 3 3 0 0 0,要么模 3 3 3 1 1 1,相当于要么让所有数都减去同样的个数,要么在这基础上挑一个数多减一次。
定义 d p i , j dp_{i,j} dpi,j表示在构造一个长度为 i i i的串,让每个数减去 i − j 3 \frac{i-j}{3} 3ij,并且,进行 j j j次挑一个数多减 1 1 1的操作,得到的总系数和。
可以得到其转移式:
d p i , j = d p i − 1 , k − 1 + ∑ k = 1 min ⁡ ( i , 3 j + 1 ) [ k ≡ 1 ( m o d 3 ) ] d p i − k , j − k − 1 3 − 3 [ k ≡ 0 ( m o d 3 ) ] d p i − k , j − k 3 dp_{i,j}=dp_{i-1,k-1}+\sum_{k=1}^{\min(i,3j+1)}[k\equiv 1\pmod{3}]dp_{i-k,j-\frac{k-1}{3}}-3[k\equiv 0\pmod{3}]dp_{i-k,j-\frac{k}{3}} dpi,j=dpi1,k1+k=1min(i,3j+1)[k1(mod3)]dpik,j3k13[k0(mod3)]dpik,j3k其中 d p i − 1 , k − 1 dp_{i-1,k-1} dpi1,k1表示这个位置没被选择容斥, 3 3 3的系数是由于模 3 3 3 0 0 0的情况有 A B C , B C A , C A B ABC,BCA,CAB ABC,BCA,CAB三种结构,模 3 3 3 1 1 1的情况结构是固定的,没必要乘 3 3 3
答案显然可以通过 d p dp dp值算出来,有
A n s = ∑ ( n − 3 i A − i , B − i , C − i ) d p n , n − i Ans=\sum\binom{n-3i}{A-i,B-i,C-i}dp_{n,n-i} Ans=(Ai,Bi,Cin3i)dpn,ni
再对上面的式子根据模 3 3 3余数加个前缀和优化,我们就得到一个简单的 O ( n 2 ) O\left(n^2\right) O(n2) d p dp dp做法了。

接下来让我们来看看怎么优化上面的这个 d p dp dp,不妨考虑生成函数。
定义二元生成函数 G ( x , y ) G(x,y) G(x,y),其中 [ x i y j ] G [x^iy^j]G [xiyj]G代表 d p i , j dp_{i,j} dpi,j的值。
通过 G G G的递推式可以得到,
F = x + ∑ i = 1 ∞ x 3 i + 1 y i − 3 x 3 i y i = x − 3 x 3 y 1 − x 3 y G = ∑ i = 0 ∞ F i = 1 1 − F ( x , y ) = 1 − x 3 y 1 − x + 2 x 3 y F=x+\sum_{i=1}^{\infty}x^{3i+1}y^i-3x^{3i}y^i=\frac{x-3x^3y}{1-x^3y}\\ G=\sum_{i=0}^{\infty} F^i=\frac{1}{1-F(x,y)}=\frac{1-x^3y}{1-x+2x^3y}\\ F=x+i=1x3i+1yi3x3iyi=1x3yx3x3yG=i=0Fi=1F(x,y)1=1x+2x3y1x3y同样,考虑现在答案怎么计算。
A n s = ∑ i = 0 min ⁡ ( A , B , C ) ( n − 3 i A − i , B − i , C − i ) [ x n y i ] G Ans=\sum_{i=0}^{\min(A,B,C)}\binom{n-3i}{A-i,B-i,C-i}[x^ny^i]G Ans=i=0min(A,B,C)(Ai,Bi,Cin3i)[xnyi]G G ′ = 1 1 − x + 2 x 3 y = ∑ ( x − 2 x 3 y ) i G'=\frac{1}{1-x+2x^3y}=\sum (x-2x^3y)^i G=1x+2x3y1=(x2x3y)i,显然有 [ x n y m ] G = [ x n y m ] G ′ − [ x n − 3 y m − 1 ] G ′ [x^ny^m]G=[x^ny^m]G'-[x^{n-3}y^{m-1}]G' [xnym]G=[xnym]G[xn3ym1]G>
现在我们就只需要考虑 [ x n y m ] G ′ [x^ny^m]G' [xnym]G怎么算了,这个好搞,可以解方程,看两边分别贡献了多少。
a − 3 b = n , b = m ⇒ a = n − 3 m a-3b=n,b=m\Rightarrow a=n-3m a3b=n,b=ma=n3m,所以根据二项式定理可得,
[ x n y m ] G ′ = ( n − 2 m m ) ( − 2 ) m [x^ny^m]G'=\binom{n-2m}{m}(-2)^m [xnym]G=(mn2m)(2)m带到上面的式子里面去就可以快速算出答案了。

时间复杂度 O ( A + B + C ) O\left(A+B+C\right) O(A+B+C)

源码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXN 3000005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mo=998244353;
template<typename _T>
void read(_T &x){
    _T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
    x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int nA,nB,nC,n,fac[MAXN],inv[MAXN],ff[MAXN],pw2[MAXN],ans;
void init(){
    fac[0]=fac[1]=inv[0]=inv[1]=ff[1]=pw2[0]=1;
    for(int i=2;i<=n;i++)
        fac[i]=1ll*i*fac[i-1]%mo,
        ff[i]=1ll*(mo-mo/i)*ff[mo%i]%mo,
        inv[i]=1ll*ff[i]*inv[i-1]%mo;
    for(int i=1;i<=n;i++)pw2[i]=mo-add(pw2[i-1],pw2[i-1],mo);
}
int C(int x,int y){
    if(x<0||y<0||x<y)return 0;
    return 1ll*fac[x]*inv[y]%mo*inv[x-y]%mo;
}
int work(int x,int y){
    if(x<0||y<0||x<y+y)return 0;
    return 1ll*pw2[y]*C(x-y-y,y)%mo;
}
signed main(){
    read(nA);read(nB);read(nC);n=nA+nB+nC;init();
    for(int i=0;i<=min(nA,min(nB,nC));i++){
        int tmp=add(work(n,i),mo-work(n-3,i-1),mo);
        Add(ans,1ll*C(n-i*3,nA-i)*C(n-i*2-nA,nB-i)%mo*tmp%mo,mo);
    }
    printf("%d\n",ans);
    return 0;
}

谢谢!!!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值