已经进入每天都有模拟赛的阶段了,不过快到 wzr 的出题 round 了。其实两位大佬的题目都"很有意思"呢!
1.BZOJ4927 / LOJ6065 2017山东一轮集训Day3 第一题
题意
给定 n n n 根直的木棍,要从中选出 6 6 6 根木棍,满足:能用这 6 6 6 根木棍拼出一个正方形。注意木棍不能弯折。问方案数。
n ≤ 5000 n \leq 5000 n≤5000, 1 ≤ a i ≤ 1 0 7 1 \leq a_i \leq 10 ^ 7 1≤ai≤107。
思路
先分配每条边的木棍数: 1 + 1 + 2 + 2 1+1+2+2 1+1+2+2 或 1 + 1 + 1 + 3 1+1+1+3 1+1+1+3。
方案不同在于,选的木棍的下标不同,不同下标的木棍长度可能相同,方案内部没有顺序区分。所以我们先对木棍去重并排序,得到 b i b_i bi 数组, i i i 种木棍各自的数量为 c n t i cnt_i cnti。
对于第一种情况,我们从小到大枚举 1 1 1 根边长 b i b_i bi。然后枚举 b l + b r = b i b_l+b_r=b_i bl+br=bi 的每对 ( l , r ) (l,r) (l,r),解决第一个 2 2 2 根边,再与 2 2 2 根长度和亦为 b i b_i bi 的方案进行配对。
枚举到一对 ( l , r ) (l,r) (l,r) 后,记 f i f_i fi 表示,用 2 2 2 根组成 a i a_i ai 的方案数,理应是 c n t l , c n t r cnt_l,cnt_r cntl,cntr 中各选一个,再 × f i \times f_i ×fi。
r e t i ← c n t l × c n t r × f i ret_i\leftarrow cnt_l\times cnt_r\times f_i reti←cntl×cntr×fi
我们发现 ( l , r ) (l,r) (l,r) 可能会并入 f i f_i fi 被统计,又因为没有顺序区分,于是动态维护 f i f_i fi,表示从小到大枚举到 b l b_l bl,用 < b l <b_l <bl 的一条边组成和为 b i b_i bi 的方案数,然后再单独考虑 ( l , r ) (l,r) (l,r) 和 ( l , r ) (l,r) (l,r) 配对的情况,即各选两个相乘。于是可以做到不重复。
r e t i ← ( c n t l 2 ) × ( c n t r 2 ) ret_i\leftarrow\binom{cnt_l}{2}\times \binom{cnt_r}{2} reti←(2cntl)×(2cntr)
注意特判 l = r l=r l=r 的情况,这个要一种边里面选 2 / 4 2/4 2/4 个:
r e t i ← ( c n t l 2 ) × f i + ( c n t l 4 ) , l = r ret_i\leftarrow \binom{cnt_l}{2}\times f_i+\binom{cnt_l}{4},l=r reti←(2cntl)×fi+(4cntl),l=r
再实时更新 f i f_i fi 即可,因为只要组成一对,所以新增方案数为 l , r l,r l,r 各选一个:
f i ← { c n t l × c n t r c n t l × ( c n t l − 1 ) l ≠ r l = r f_i\leftarrow \begin{cases} cnt_l\times cnt_r \\ cnt_l\times (cnt_l-1) \end{cases} \begin{align*} l\neq r \\ l=r \end{align*} fi←{cntl×cntrcntl×(cntl−1)l=rl=r
枚举 l , r l,r l,r 可以双指针 O ( n ) O(n) O(n) 扫,就不用 lower_bound 多一个 log \log log 了。注意 r e t i ret_i reti 外面乘上 i i i 种木棍选 2 2 2 个。
cpp
for(int i=1;i<=tot;i++)//边长
{
if(cnt[i]>=2)
{
ll ret=0;
for(int l=1,r=i-1;l<=r;l++)//小边12
{
//r=lower_bound(b+1,b+i,b[i]-b[l])-b;
while(l<=r&&b[l]+b[r]>b[i])r--;
if(b[l]+b[r]<b[i])continue;
if(l>r)break;
//b[l]+b[r]=b[i]
ll lc=cnt[l],rc=cnt[r];
if(l!=r)
{
ret+=Cx2(lc)*Cx2(rc)+lc*rc*f[i];
f[i]+=lc*rc;
}
else
{
ret+=Cx4(lc)+Cx2(lc)*f[i];
f[i]+=lc*(lc-1);
}
}
ans+=ret*Cx2(cnt[i]);
}
}
对于第二种情况,我们依然想要从小到大枚举边长,然后枚举 3 3 3 根边其中一边 b j b_j bj。令 g l e n g_{len} glen 表示,用 2 2 2 根木棍组成长度为 l e n len len 的边的方案数:
r e t i ← 1 × g b i − b j × ( c n t i 3 ) ret_i\leftarrow 1\times g_{b_i-b_j}\times \binom{cnt_i}{3} reti←1×gbi−bj×(3cnti)
然后再更新使用 b i b_i bi 对 g g g 数组其他长度带来的新贡献,每次只用更新 b i b_i bi,因为 b i + 1 b_{i+1} bi+1 要使用的 g l e n g_{len} glen 满足 l e n < b i + 1 len<b_{i+1} len<bi+1。
g b i + b j ← c n t i × c n t j , j < i g_{b_i+b_j}\leftarrow cnt_i\times cnt_j,j<i gbi+bj←cnti×cntj,j<i
但是这样更新会出现一个问题: g b i − b j g_{b_i-b_j} gbi−bj 可能包含使用 b j b_j bj 边的情况,即 b j + ( b j + x ) = b i b_j+(b_j+x)=b_i bj+(bj+x)=bi, g b i − b j g_{b_i-b_j} gbi−bj 包含 b j b_j bj 的 c n t j cnt_j cntj 条边匹配 x x x 对应的 c n t cnt cnt,但是实际应为 c n t j − 1 cnt_j-1 cntj−1,而且还会漏掉 b j + ( b j + b j ) = b i b_j+(b_j+b_j)=b_i bj+(bj+bj)=bi 的情况。
(错误示范)
cpp
for(int i=1;i<=m;i++)//边长
{
if(cnt[i]>=3)
{
for(int j=i-1;j>=1;j--)
ans+=cnt[j]*Cx3(cnt[i])*g[b[i]-b[j]];
}
for(int j=1;j<i;j++)
g[b[i]+b[j]]+=cnt[i]*cnt[j];
}
我们发现并不好去重,因为 g g g 不能维护所有的和的方案数,不然用 unordered_map 都开不下 n 2 n^2 n2 个和!
于是考虑摒弃每种边之前匹配的情况,我们单独考虑每一根木棍 a i a_i ai,作为 3 3 3 根边的最长木棍,带来的贡献。枚举比 a i a_i ai 大的 b j b_j bj 作为边长:
r e t i ′ ← 1 × g b j − a i × ( c n t j 3 ) ret'i\leftarrow 1\times g{b_j-a_i}\times \binom{cnt_j}{3} reti′←1×gbj−ai×(3cntj)
因为对于单独一个下标为 i i i 的木棍,此时 i i i 还未参与 g g g 的方案,因此不会算重。计算完木棍 i i i 带来的贡献之后,就更新 g g g 即可。
其实第一种情况也可以用类似的方法去做:先枚举 b l b_l bl 再枚举 b i b_i bi,lower_bound 得到一个 b r b_r br,
具体细节见代码:
cpp
#pragma GCC optimise(2)
#pragma GCC optimise(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=5003,S=1e7+2;
int n,a[N],R;
int tot,b[N],cnt[N],ori[N];
ll f[N];
int g[S];
ll Cx2(ll x)
{
if(x<2)return 0;
return x*(x-1)/2;
}
ll Cx3(ll x)
{
if(x<3)return 0;
return x*(x-1)*(x-2)/6;
}
ll Cx4(ll x)
{
if(x<4)return 0;
return x*(x-1)*(x-2)*(x-3)/24;
}
int main()
{
freopen("stick.in","r",stdin);
freopen("stick.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
R=a[n];
for(int i=1;i<=n;i++)
{
if(a[i]!=a[i-1])b[++tot]=a[i];
cnt[tot]++;
ori[i]=tot;
}
ll ans=0;
/* 情况1的第二种写法
for(int l=1;l<=tot;l++)
{
for(int i=l+1;i<=tot;i++)
{
ll r=lower_bound(b+1,b+i,b[i]-b[l])-b;
if(b[l]+b[r]!=b[i])continue;
if(l>r)continue;
if(cnt[i]>=2)
{
ll ret=0;
ll lc=cnt[l],rc=cnt[r];
if(l!=r)
{
ret+=Cx2(lc)*Cx2(rc)+lc*rc*f[i];
f[i]+=lc*rc;
}
else
{
ret+=Cx4(lc)+Cx2(lc)*f[i];
f[i]+=lc*(lc-1);
}
ans+=ret*Cx2(cnt[i]);
}
}
}*/
for(int i=1;i<=tot;i++)//边长
{
if(cnt[i]>=2)
{
ll ret=0;
for(int l=1,r=i-1;l<=r;l++)//小边12
{
//r=lower_bound(b+1,b+i,b[i]-b[l])-b;//必须同推
while(l<=r&&b[l]+b[r]>b[i])r--;
if(b[l]+b[r]<b[i])continue;
if(l>r)break;
//b[l]+b[r]=b[i]
ll lc=cnt[l],rc=cnt[r];
if(l!=r)
{
ret+=Cx2(lc)*Cx2(rc)+lc*rc*f[i];
f[i]+=lc*rc;
}
else
{
ret+=Cx4(lc)+Cx2(lc)*f[i];
f[i]+=lc*(lc-1);
}
}
ans+=ret*Cx2(cnt[i]);
}
}
// cout<<ans<<endl;
for(int i=1;i<=n;i++)//最长小边1
{
for(int j=tot;j>ori[i];j--)//边长
ans+=1ll*Cx3(cnt[j])*g[b[j]-a[i]];
for(int j=1;j<i;j++)
if(a[i]+a[j]<=R)g[a[i]+a[j]]++;
}
printf("%lld",ans);
return 0;
}
2.洛谷 P10299 CCC2024S5 Chocolate Bar Partition
3.洛谷 P9312 EGOI2021 Lanterns / 灯笼
4.洛谷 P5642 人造情感
这后面三题,第二题实在难理解,后两题两道大黑呢qwq。接下来是 GJOI 10.8.
1.AT_arc132_d Between Two Binary Strings
题意
给定 n , m n,m n,m,以及两个由 n n n 个 0 和 m m m 个 1 组成的字符串 s 1 , s 2 s_1,s_2 s1,s2。记 f ( s 1 , s 2 ) f(s_1,s_2) f(s1,s2) 表示每次交换 s 1 s_1 s1 的两个相邻字符,变成 s 2 s_2 s2 的最少交换次数。
对于长度为 n + m n+m n+m 的字符串 s s s,记 g ( s ) = ∑ i = 1 n + m − 1 [ s i = s i + 1 ] g(s)=\displaystyle\sum_{i=1}^{n+m-1}[s_i=s_{i+1}] g(s)=i=1∑n+m−1[si=si+1],即 s s s 相邻字符相同的对数。求最大的 g ( s 3 ) g(s_3) g(s3),满足 f ( s 1 , s 2 ) = f ( s 1 , s 3 ) + f ( s 3 , s 2 ) f(s_1,s_2)=f(s_1,s_3)+f(s_3,s_2) f(s1,s2)=f(s1,s3)+f(s3,s2)。
思路
虽然计算 f f f 看起来比较经典,但是我本身是不会的,还是赛时大佬一眼秒的性质。
先考虑计算 f ( s 1 , s 2 ) f(s_1,s_2) f(s1,s2),举个例子:
n=4,m=5
s1 = 111000110
s2 = 011011001
首先我们不会交换相邻的 0 / 1 0/1 0/1,因为是无用的。我们想要把 s 1 s_1 s1 第 1 1 1 个 1 从下标 1 1 1 换到下标 2 2 2 去。这里我们需要把下标 4 4 4 的 0 换到下标 1 1 1 去,我们发现第 1 ∼ 3 1\sim 3 1∼3 个 1 距离各自的 1 1 1 都少 1 1 1 的单位,而 0 恰好换了 3 3 3 次。
于是这个结论是: s 1 , s 2 s_1,s_2 s1,s2 的第 i i i 个 1 的下标之差的绝对值之和。如上述例子 f ( s 1 , s 2 ) = 1 + 1 + 2 + 1 + 1 = 6 f(s_1,s_2)=1+1+2+1+1=6 f(s1,s2)=1+1+2+1+1=6。
由此我们可以确定 s 3 s_3 s3 的形态: s 3 s_3 s3 是 s 1 s_1 s1 向 s 2 s_2 s2 转化过程中的某个字符串。 s 3 s_3 s3 的第 i i i 个 1,应恰在 s 1 , s 2 s_1,s_2 s1,s2 的第 i i i 个 1 之间。
于是就产生了, s 3 s_3 s3 的第 i i i 个 1 的放置区间 [ l , r ] [l,r] [l,r],问题转化为如何放置 1 使得连续 1 的长度尽可能长(相同的数尽量放在一起)。
因为有了超速检测的经验教训,我们想到右端点排序(本题区间的左右端点天然有序),然后尽量把点往右放------尽可能进入后面的区间,使得可以接续。
那第一个 1 自然根据这个经典的贪心,填到右端点去。
cpp
ll sol(ll st)
{
ll las;
memset(b,0,sizeof(b));
b[las=st]=1;
for(int i=2;i<=m;i++)
{
if(s3[i].l<=las+1&&las+1<=s3[i].r)b[++las]=1;
else b[las=s3[i].r]=1;
}
ll ret=0;
for(int i=1;i<o;i++)
ret+=(b[i]==b[i+1]);
return ret;
}
...
ans=max(ans,sol(s3[1].r));//s3第1段1的右端点
但是我们发现一个反例:
n=4,m=3
s1 = 0100011
s2 = 1000101
按照上面的贪心,第 1 1 1 个 1 要填到下标 2 2 2 去。但是这个 1 不仅后面没有 1 和它相连,而且还切断了原本较长的一段 0:
s3 = 0100011 //原贪心
s3 = 1000011 //实际最优
于是我们应该防止 1 把 0 切断,即把 1 扔到下标 1 去,即多一个判断从第 1 1 1 区间左端点的情况(实际只需 s3[1].l==1 时)。
为了保险,我还正反都做了一边。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=6e5+9;
ll n,m,o;
char s1[N],s2[N];
ll p1[N],p2[N],t1,t2;
struct node
{
ll l,r;
}s3[N],s3r[N];
bool b[N];
ll sol(ll st)
{
ll las;
memset(b,0,sizeof(b));
b[las=st]=1;
for(int i=2;i<=m;i++)
{
if(s3[i].l<=las+1&&las+1<=s3[i].r)b[++las]=1;
else b[las=s3[i].r]=1;
}
ll ret=0;
for(int i=1;i<o;i++)
ret+=(b[i]==b[i+1]);
return ret;
}
int main()
{
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
scanf("%lld%lld",&n,&m);
o=n+m;
scanf("%s%s",s1+1,s2+1);
for(int i=1;i<=o;i++)
{
if(s1[i]=='1')p1[++t1]=i;
if(s2[i]=='1')p2[++t2]=i;
}
for(int i=1;i<=m;i++)
s3[i]=(node){min(p1[i],p2[i]),max(p1[i],p2[i])};
ll ans=0;
ans=max(ans,max(sol(s3[1].l),sol(s3[1].r)));
// if(s3[2].l<=s3[1].r&&s3[1].l<s3[2].l)ans=max(ans,sol(s3[2].l-1));//左端点,实则不优
for(int i=1;i<=m;i++)
{
s3r[i].l=o-s3[i].l+1;
s3r[i].r=o-s3[i].r+1;
}
for(int i=1;i<=m;i++)
s3[i]=s3r[m-i+1];
ans=max(ans,max(sol(s3[1].l),sol(s3[1].r)));
// if(s3[2].l<=s3[1].r&&s3[1].l<s3[2].l)ans=max(ans,sol(s3[2].l-1));//左端点,实则不优
printf("%lld",ans);
return 0;
}
2.P6294 eJOI 2017 游戏
题意

3 ≤ n ≤ 1 0 5 3\le n\le 10^5 3≤n≤105, 1 ≤ m ≤ 1 0 3 1\le m\le 10^3 1≤m≤103, a i ∈ [ 1 , 1 0 9 ] a_i\in[1,10^9] ai∈[1,109]。
洛谷 P6294 题意:Alice 的总和减去 Bob 的总和。
思路
赛时 O ( n m log n ) O(nm\log n) O(nmlogn) 写堆维护,狂写 64pts。没想到正解就是在暴力的基础上小优化一下。
设已经加入的数的多重有序集为 S S S,当前新加入 a i a_i ai。若 a i ≥ S m a x a_i\ge S_{max} ai≥Smax, a i a_i ai 会被直接拿掉,是不用加入排序的;否则就加入 S S S。等到加完数的时候,或者没法把新加入的数拿走,就降序遍历 S S S,拿走 S S S 的元素。
如果我们维护一个指向 S m a x S_{max} Smax 的指针 c u r cur cur( S S S 的最后一个元素),我们发现 c u r cur cur 不会增只会降。因为新加入的数比它大就会直接拿走, c u r cur cur 不会往大了跑。
具体地,我们将原数组离散化,将离散化后的 a 1 ∼ p − 1 a_{1\sim p-1} a1∼p−1 加入桶, c u r = max { a i } cur=\max\{a_i\} cur=max{ai} 作为桶上存在 > 0 >0 >0 的最大值。依次加入 i ∈ [ p , n ] i\in[p,n] i∈[p,n],如果 a i ≥ c u r a_i\ge cur ai≥cur 直接拿走。
否则把 a i a_i ai 加入桶。然后在桶上,往小移动指针 c u r cur cur 直到出现出现次数 > 0 >0 >0 的就是当前最大值了。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9;
ll n,Q;
ll a[N];
ll aa[N],nn;
ll cnt[N];
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%lld%lld",&n,&Q);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
aa[i]=a[i];
}
sort(aa+1,aa+n+1);
nn=unique(aa+1,aa+n+1)-aa-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(aa+1,aa+nn+1,a[i])-aa;
while(Q--)
{
ll p,sa=0,sb=0,cur=0;
scanf("%lld",&p);
for(int i=1;i<=p;i++)
{
cur=max(cur,a[i]);
cnt[a[i]]++;
}
sa+=aa[cur];
cnt[cur]--;
for(int i=2;i<=n;i++)
{
if(i+p-1<=n)//加数
{
cnt[a[i+p-1]]++;
if(a[i+p-1]>=cur)
{
if(i&1)sa+=aa[a[i+p-1]];
else sb+=aa[a[i+p-1]];
cnt[a[i+p-1]]--;
continue;
}
}
while(!cnt[cur])cur--;
if(i&1)sa+=aa[cur];
else sb+=aa[cur];
cnt[cur]--;
}
printf("%lld\n",sa);
}
return 0;
}
洛谷题目代码敬请自己修改。
3.AT_arc119_f AtCoder Express 3
题意

2 ≤ n ≤ 4000 2\le n\le 4000 2≤n≤4000, 1 ≤ k ≤ n 1\le k\le n 1≤k≤n。
AT_arc119_f: n n n 为当前题面 + 1 +1 +1。本篇博客按照 n n n 比原题面多 1 1 1 的数据来讲解。
思路
考虑平推每一位,分别判断 A、B 和 ? 填哪一个。
因为这里是计数,所以维护到每一位最短距离没什么用,于是把距离放到状态去。又因为一个点肯定会从距离其最近的白点或黑点转移过来,所以设 f i , j , k , o p f_{i,j,k,op} fi,j,k,op 表示,走到第 i i i 个位置, o p = 0 / 1 op=0/1 op=0/1 表示填白 / 黑,距离起点最远的白点 / 黑点的(最短)距离分别为 j / k j/k j/k 的方案数。
先看路径两个维度的变化,假若第 i i i 位填白色:
- 若 i − 1 i-1 i−1 为白色:
- 最远白点距离为 j + 1 j+1 j+1(均指最短距离,下同);
- 最远黑点距离不变;
- 若 i − 1 i-1 i−1 为黑色:
- 最远白点可以从上一块白色走过来: j + 1 j+1 j+1,也可以隔壁黑色走一步过来,即 min ( j + 1 , k + 1 ) \min(j+1,k+1) min(j+1,k+1);
- 最远黑点距离 k k k 就在 i − 1 i-1 i−1,对应上一状态,但是现在多了一个白点在后面,其实可以从 i i i 走回来即 min ( j + 2 , k ) \min(j+2,k) min(j+2,k)。
所以最终有:
{ f i , j + 1 , k , 0 ← f i − 1 , j , k , 0 f i , min ( j + 1 , k + 1 ) , min ( j + 2 , k ) , 0 ← f i − 1 , j , k , 1 s i = A/? \begin{cases} f_{i,j+1,k,0}\leftarrow f_{i-1,j,k,0} \\ f_{i,\min(j+1,k+1),\min(j+2,k),0}\leftarrow f_{i-1,j,k,1} \end{cases}\begin{align*} s_i=\text{A/?} \end{align*} {fi,j+1,k,0←fi−1,j,k,0fi,min(j+1,k+1),min(j+2,k),0←fi−1,j,k,1si=A/?
同理有第 i i i 位填黑色的转移:
{ f i , j , k + 1 , 1 ← f i − 1 , j , k , 1 f i , min ( j , k + 2 ) , min ( j + 1 , k + 1 ) , 1 ← f i − 1 , j , k , 0 s i = B/? \begin{cases} f_{i,j,k+1,1}\leftarrow f_{i-1,j,k,1} \\ f_{i,\min(j,k+2),\min(j+1,k+1),1}\leftarrow f_{i-1,j,k,0} \end{cases}\begin{align*} s_i=\text{B/?} \end{align*} {fi,j,k+1,1←fi−1,j,k,1fi,min(j,k+2),min(j+1,k+1),1←fi−1,j,k,0si=B/?
最后答案就是 ∑ j = 0 m + 2 ∑ k = j − 2 j + 2 f n − 1 , j , k − j , 0 / 1 × [ min ( j , k ) + 1 ≤ m ] \displaystyle\sum_{j=0}^{m+2}\sum {k=j-2}^{j+2}f{n-1,j,k-j,0/1}\times [\min(j,k)+1\le m] j=0∑m+2k=j−2∑j+2fn−1,j,k−j,0/1×[min(j,k)+1≤m]。 j j j 都要顶到 m + 1 m+1 m+1 给 k = j − 2 = m − 1 k=j-2=m-1 k=j−2=m−1 预留。
但是这样转移是时空复杂度 O ( n m 2 ) O(nm^2) O(nm2) 的,我们发现两个距离维度有点冗余了,考虑优化状态。
对于 j j j:设 i i i 为白,那么 j j j 应当 ≥ k − 1 \ge k-1 ≥k−1,因为最远黑点的 k k k,会有和 i i i 同一块白色的第一个白色点 + 1 +1 +1 更新回去,极限情况就是 i i i 是第一个白色点。
如果 j j j 比 k k k 大很多,就不太会用 j j j 来参与两个距离维度的更新,于是 j j j 又会有上界。又若 j > k + 2 j>k+2 j>k+2,填一个黑就会在 min ( j , k + 2 ) \min(j,k+2) min(j,k+2), j j j 被"淘汰掉"。
反过来思考的 k k k 的上下界亦然, k ≥ j − 1 k\ge j-1 k≥j−1, k > j + 2 k>j+2 k>j+2,于是 ∣ j − k ∣ ≤ 2 |j-k|\le 2 ∣j−k∣≤2,即 k − j ∈ [ − 2 , 2 ] k-j\in[-2,2] k−j∈[−2,2]。我们将 k k k 那一维改成 k − j k-j k−j 差值,就可以消掉一个 O ( m ) O(m) O(m)。于是时空复杂度 O ( 4 n m ) O(4nm) O(4nm)。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=4003,mod=1e9+7,D=2;
ll n,m;
char s[N];
ll f[N][N][6][2];
void upd(ll i,ll j,ll k,ll op,ll ad)
{
j=min(j,k+2);
k=min(k,j+2);
f[i][j][k-j+D][op]=(f[i][j][k-j+D][op]+ad)%mod;
}
int main()
{
// freopen("path.in","r",stdin);
// freopen("path.out","w",stdout);
scanf("%lld%lld%s",&n,&m,s+1);
f[0][0][0+D][0]=1;
// n++;
for(int i=1;i<n;i++)
{
for(int j=0;j<=m+1;j++)
{
for(int k=j-2;k<=j+2;k++)
{
if(s[i]!='B')
{
upd(i,j+1,k,0,f[i-1][j][k-j+D][0]);
upd(i,min(j+1,k+1),min(j+2,k),0,f[i-1][j][k-j+D][1]);
}
if(s[i]!='A')
{
upd(i,j,k+1,1,f[i-1][j][k-j+D][1]);
upd(i,min(j,k+2),min(j+1,k+1),1,f[i-1][j][k-j+D][0]);
}
}
}
}
ll ans=0;
for(int j=0;j<=m+1;j++)
{
for(int k=j-2;k<=j+2;k++)
{
if(min(j+1,k+1)<=m)
{
ans=(ans+f[n-1][j][k-j+D][0])%mod;
ans=(ans+f[n-1][j][k-j+D][1])%mod;
}
}
}
printf("%lld",ans);
return 0;
}
南海云课堂处题面敬请自行修改。