题目
题目描述
有一部 精彩绝伦的 国漫,一共
n
+
1
n+1
n+1 集,编号为
0
0
0 到
n
n
n 。每一集中的主角,要么为「阿七」,要么为「阿柒」;特别的,第
0
0
0 集和第
n
n
n 集两个主角都有。现在电视台提供了一个 “根据你的喜好自动推荐” 换台模式。具体来说,如果当前在观看第
i
i
i 集,按一下换台键之后,会立刻找到接下来的第一个主角相同的剧集,然后跳转;第
0
0
0 集和第
n
n
n 集等效于二者兼具,而做选择不需要按任何键。当然传统的换台也被保留了下来,按一下左键,进入上一集;按一下右键,进入下一集。
现在「湘妹」在看第
0
0
0 集。她只想赶紧看最后一集,赶紧去学 “我今天就要带这个傻子走”,而她只想按最多
k
k
k 次按键。糟糕的是,她并不知道中间的剧集的主角是谁,只能问 当事人「小剪刀」;它的记忆又比较模糊,只能确定一部分,剩下的就无能为力了。
如果有 r r r 集的主角是不确定的,显然就有 2 r 2^r 2r 种不同的情况。在所有这些情况中,有多少种情况能够让「湘妹」完成她的梦想?请你告诉「湘妹」。
数据范围与提示
n
≤
4000
n\le 4000
n≤4000 且
k
≤
n
+
1
2
k\le\frac{n+1}{2}
k≤2n+1 。不保证「小剪刀」到底能记住多少集的主角,因为年轻人,剪刀不是这样用的,要把气集中在尖尖上……
思路
《关于魔改题面被骂这件事》:建议大家看原本的题面。
这种计数题,肯定要 d p \tt dp dp 嘛。状态是什么呢?显然是最短路距离嘛。设置为到哪里的最短路距离呢?
先来想象一下整个旅程。当你位于一段连续的 A A A 的开头时,你只有两种选择:后退一步,使用 B B B 跳过这一段;一直往前走,老老实实地走过这一段。因为 没有一种路径可以同时跳过后一个交界处的两个点。是不是有点抽象?举个例子: A ⋯ A B ⋯ B B A A ⋯ A A\cdots AB\cdots B{\color{green}BA}A\cdots A A⋯AB⋯BBAA⋯A,你能构造出一条路径,那两个绿色标记的站台都不经过吗?不能。无论用 A e x p r e s s A\;\rm express Aexpress 还是 B e x p r e s s B\;\rm express Bexpress 都做不到。
所以,我们只需要记录这样的两个站台的最短路距离。这好像是 O ( k 2 ) \mathcal O(k^2) O(k2) 的,实则不然。根据最短路的定义,二者的差值必然不超过 1 1 1,否则就可以内部更新一次。所以这是 O ( k ) \mathcal O(k) O(k) 的。
定义
d
p
(
i
,
j
,
d
,
c
)
dp(i,j,d,c)
dp(i,j,d,c) 为,站台
i
i
i 和
i
+
1
i+1
i+1 最短路距离分别为
j
j
j 和
j
+
d
j+d
j+d,并且站台
i
+
1
i+1
i+1 是类型
c
c
c 。这是
O
(
n
k
)
\mathcal O(nk)
O(nk) 的。转移的时候,由于有 ?
的存在,需要枚举下一个转变点(非类型
c
c
c 的站台),是
O
(
n
)
\mathcal O(n)
O(n) 转移。
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
rep(i,0,n-2) rep(j,0,k) rep(d,-1,1)
rep(c,0,1) rep(p,i+1,n-1){
if(str[p+1]-'A' != c){ // may be a stop
int d1 = min(j+2,j+d+(p-i)-1);
int d2 = min(j+1,j+d+(p-i));
if(d1 <= k+1) // useful
add(dp[p][d1][d2-d1+1][c^1],
dp[i][j][d+1][c]);
}
if(str[p+1]-'A' == (c^1)) // must stop
break; // cannot keep on
}
我们细心一点就会发现,当
p
−
i
≥
4
p-i\ge 4
p−i≥4 时,哪怕
d
=
−
1
d=-1
d=−1,也有
{
j
+
d
+
(
p
−
i
)
−
1
≥
j
+
2
j
+
d
+
(
p
−
i
)
≥
j
+
1
\begin{cases} j+d+(p-i)-1\ge j+2\\ j+d+(p-i)\ge j+1 \end{cases}
{j+d+(p−i)−1≥j+2j+d+(p−i)≥j+1
也就是说,后面都会转移到 f ( p , j + 2 , − 1 , 1 − c ) f(p,j+2,-1,1-c) f(p,j+2,−1,1−c) 的状态。那就打个懒标记呗!很容易实现。
于是转移变成了 O ( 4 ) \mathcal O(4) O(4),总复杂度 O ( n k ) \mathcal O(nk) O(nk),只是常数高达 3 × 2 × 4 = 24 3\times 2\times 4=24 3×2×4=24,已经接近 log \log log 了……
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int_ readint(){
int_ a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int_ x){
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int Mod = 1e9+7;
inline void add(int &x,const int &y){
(x += y) >= Mod ? (x -= Mod) : 0;
}
const int MaxN = 4005;
// dis = (j, j + dif), color of i+1 is fourth
int dp[MaxN][MaxN][3][2];
int lazy[MaxN][MaxN][3][2];
char str[MaxN];
int main(){
int n = readint(), k = readint();
scanf("%s",str+1);
if(str[1] == '?') // dis = (0,1)
dp[0][0][2][0] = dp[0][0][2][1] = 1;
else dp[0][0][2][str[1]-'A'] = 1;
for(int i=0; i<=n-1; ++i)
for(int j=0; j<=k+1; ++j) // k+1 is useful
for(int d=-1; d<=1; ++d) // dif
for(int c=0; c<2; ++c){
if(str[i+1]-'A' != (c^1))
add(dp[i][j][d+1][c],lazy[i][j][d+1][c]);
if(str[i+1]-'A' != c) // can be passed on
add(lazy[i+1][j][d+1][c],lazy[i][j][d+1][c]);
if(dp[i][j][d+1][c]){
bool xez_yyds = true;
for(int p=i+1; p<=n-1&&p<=i+3; ++p){
if(str[p+1]-'A' != c){ // may be a stop
int d1 = min(j+2,j+d+(p-i)-1);
int d2 = min(j+1,j+d+(p-i));
if(d1 <= k+1) // useful
add(dp[p][d1][d2-d1+1][c^1],
dp[i][j][d+1][c]);
}
if(str[p+1]-'A' == (c^1)){ // must stop
xez_yyds = false;
break; // cannot keep on
}
}
if(xez_yyds && i+4 <= n-1)
add(lazy[i+4][j+2][0][c^1],
dp[i][j][d+1][c]);
}
}
int ans = 0;
rep(j,0,k+1) rep(d,-1,1) rep(c,0,1)
if(j+d <= k) // satisfactory
add(ans,dp[n-1][j][d+1][c]);
printf("%d\n",ans);
return 0;
}
S Y t q l \sf SY\;tql SYtql
请看 H a n d I n D e v i l \sf HandInDevil HandInDevil 的博客,反正我是不懂了……