Atcoder Grand Contest 033
033E Go around a Circle
题目描述
解法
如果 \(S_1=B\),我们可以翻转整个 \(S\),那么可以让 \(S_1=R\) 并且答案不变。
那么开始写必要条件,你一定要利用好 满足将棋子放在任意一个点上,都存在方案
这个限制。首先我们可以知道环上不存在相邻的两个 B
,因为起点放在 B
之间就会不合法。
我们可以特判掉 \(S\) 全是 R
的情况,那么现在的 \(S\) 就是 R/B
分段间隔出现,设这些段是 \(c_{1,2...k}\)
我们发现环上每段 R
都必须是奇数,因为如果是偶数,那么无论 \(c_1\) 是奇数或者偶数,我们都可以通过设置起点,使得走完 \(c_1\) 之后还卡在 R
段的中间。
此外 \(c_1\) 的要求还没有考虑完,如果 \(c_1\) 是奇数,那么要求每一段的 R
不超过 \(c_1\),因为如果长度超过 \(c_1\),放置在一段无法走到另一端;如果 \(c_1\) 是偶数,那么要求每一段 R
的长度不超过 \(c_1+1\),类似地我们放置在一段的下一个点无法走到另一端。
对于 \(S\) 中其他的 R
段,起点一定是环上 R
的一段,所以如果对应的 \(c_i\) 是奇数,那么只能从一段走到另一端,所以要求每一段的长度都不能超过 \(c_i\);如果对应的 \(c_i\) 是偶数,只会在端点反复横跳,所以没有限制。总结一下就是对于 \(i\in[2,k)\)(最后一段没有限制),如果 \(i\) 和 \(c_i\) 都是奇数,那么长度不超过 \(c_i\)
有了上面的必要条件之后充分性不难说明。
那么现在的限制转化为:求环的方案数,要求不能有相邻的 B
,并且 R
的连续段不能超过某个定值。
直接一维 \(dp\),设 \(f_i\) 表示前 \(i\) 个点分段(一段的 RB
一起考虑)的方案数,注意我们只对偶数位进行 \(dp\),可以用前缀和优化。最后算答案的时候考虑翻转,如果最后一段长度为 \(i\) 就有 \(i\) 种翻转方案:
时间复杂度 \(O(n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
#define int long long
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,w,ans,c[M],dp[M][2][2],g[M],f[M];char s[M];
void work()
{
dp[1][0][0]=dp[1][1][1]=1;
for(int i=2;i<=n;i++)
{
dp[i][0][0]=(dp[i-1][0][0]+dp[i-1][0][1])%MOD;
dp[i][0][1]=dp[i-1][0][0];
dp[i][1][0]=(dp[i-1][1][0]+dp[i-1][1][1])%MOD;
dp[i][1][1]=dp[i-1][1][0];
}
ans=(dp[n][0][0]+dp[n][0][1]+dp[n][1][0])%MOD;
printf("%lld\n",ans);
}
signed main()
{
n=read();m=read();scanf("%s",s+1);
for(int i=1,j=1;i<=m;i=j)
{
j=i;while(j<=m && s[i]==s[j]) j++;
c[++k]=j-i;
}
if(k==1) {work();return 0;}
if(n&1) {puts("0");return 0;}
w=c[1]+!(c[1]&1);
for(int i=3;i<k;i+=2)
if(c[i]&1) w=min(w,c[i]);
f[0]=g[0]=1;
for(int i=2;i<=n;i+=2)
{
f[i]=(g[i-2]+(i-w-3>=0?MOD-g[i-w-3]:0))%MOD;
g[i]=(g[i-2]+f[i])%MOD;
}
for(int i=2;i<=min(n,w+1);i+=2)
ans=(ans+f[n-i]*i)%MOD;
printf("%lld\n",ans);
}