1 条题解
-
0
首先容易发现操作可以加强为对 或 的段进行修改。由于可以直接把整个串改为
1
,所以 可以交换,不妨设 。把操作反过来,考虑对于一个
01
序列,能否经过若干次逆操作变为全为0
。原操作的逆操作就是选择一个长度 的全为
0
的段变为任意数。因为我们现在不知道变为什么数对我们有利,我们不妨把它们变为?
,这样我们可以选择一个长度 的不包含1
的段全变为?
,或选择一个长度 的不包含0
的段全变为?
。而目标就是把整个序列变为0
或?
,等价于变为全部是?
。由于
?
的功能严格包含0
和1
的功能,所以我们贪心地只要遇到长 的非1
段就变为?
。注意到如果我们把一个长为 的段变为了
?
,由于 ,我们可以把这个?
段无限延长,直至整个序列都变为?
。于是我们得出了如下结论:一个序列能否变为全
0
串,等价于把其中所有长度至少为 的极长连续1
段全变为0
后是否存在长至少为 的连续0
段。由容斥原理,只需求用长 的极长连续
1
段分割后每段长 且所有极长连续1
段的长度都 。考虑动态规划,记
- 表示到第 位为止最后一个连续段是长度小于 的
1
段; - 表示到第 位为止有一个长度为 不包含长度小于 的极长连续
1
段的极长段且最后一个连续段是0
; - 表示到第 位为止有一个长度为 不包含长度小于 的极长连续
1
段的极长段且最后一个连续段是1
。
用朴素方法转移是 的,使用前缀和优化即可做到 。
/** * @date: 2024.02.18 * @problem: AT_agc045_c * @tags: 数学, 组合数学, 组合计数, 思维, 动态规划 */ #include<bits/stdc++.h> using namespace std; const int mod=1e9+7; int n,a,b; long long f[5001],g[5001][5001],h[5001][5001]; long long sg[5001],sumg[5001][5001],sumh[5001]; int main(){ scanf("%d%d%d",&n,&a,&b); if(a>b)swap(a,b); f[0]=g[0][0]=h[0][0]=1; if(n<=1000){ // O(N^3) for(int i=1;i<=n;i++){ if(i<a)f[i]++; for(int j=max(0,i-a+1);j<i;j++) for(int k=1;k<=j;k++) f[i]+=g[j][k],f[i]%=mod; for(int j=1;j<=i&&j<b;j++){ for(int k=1;k<j;k++) g[i][j]+=h[i-k][j-k]; g[i][j]+=f[i-j],g[i][j]%=mod; } if(a<=i&&i<b)h[i][i]++; for(int j=1;j<=i&&j<b;j++){ for(int k=a;k<j;k++) h[i][j]+=g[i-k][j-k]; h[i][j]%=mod; } } } else{ // O(N^2) for(int i=1;i<=n;i++){ for(int j=1;j<i;j++) sg[i-1]+=g[i-1][j]; sg[i-1]%=mod; if(i<a)f[i]++; for(int j=max(0,i-a+1);j<i;j++)f[i]+=sg[j]; f[i]%=mod; for(int j=1;j<=i&&j<b;j++){ g[i][j]=(sumh[i-j]+f[i-j])%mod; sumg[i][j]=(sumg[i-1][j-1]+g[i][j])%mod; } if(a<=i&&i<b)h[i][i]++; for(int j=1;j<=i&&j<b;j++){ if(i>=a&&j>=a)h[i][j]+=sumg[i-a][j-a],h[i][j]%=mod; sumh[i-j]+=h[i][j],sumh[i-j]%=mod; } } } long long answer=1; for(int i=1;i<=n;i++)answer=answer*2%mod; answer-=f[n]; for(int i=1;i<=n;i++) answer-=g[n][i]+h[n][i]; printf("%lld\n",(answer%mod+mod)%mod); return 0; }
- 表示到第 位为止最后一个连续段是长度小于 的
- 1
信息
- ID
- 2281
- 时间
- 2000ms
- 内存
- 1024MiB
- 难度
- 6
- 标签
- (无)
- 递交数
- 7
- 已通过
- 2
- 上传者