题解 P5362 【[SDOI2019]连续子序列】
liuzhangfeiabc · · 题解
题目大意:有一个01串tm,满足tm(0)=0,tm(i)=tm(i>>1)^(i&1)。给定01串s和整数k,问有多少个本质不同的01串t满足:t是tm的子串,s是t的前缀,t的长度是|s|+k。
全场无人ac的找规律神题。
关于tm序列,有很多种解释方式,比如每个数的二进制中1的个数的奇偶性啦,从0开始每次把自身取反接在后面啦等等。对于这个题,最好的解释方式是这样的:
从0开始,每次把所有的0变成01,把所有的1变成10。
为什么这样解释最好呢?因为我们可以试着把一个tm的子串用这种方式变换回去:
假设一个串s=abcdefg
我们可以把它每2位划分为一段,即ab|cd|ef|g或a|bc|de|fg,然后把每一段按上述规则合并为一个字符。
如果某一段内两个字符相同,意味着这样划分是不合法的。
两侧也许有落单的段,但是根据规则实际上与它们成一段的另一个字符是确定的,我们也可以将其补上。
比如串10100110010,就可以切割为10|10|01|10|01|0,然后合并成110100。
可以验证:当串长>3时,合法的切割方案是唯一的。比如串1001,就只能划分为10|01,而串1010看似可以划分为1|01|0,但是由于这样缩成的是000因此不合法,所以唯一的划分方案是10|10。
因此我们就可以通过对切割方案的计数来实现对串的计数。设f(s,k)表示当前串为s,后面接k个字符的方案数。当|s|+k<=3时特判,否则枚举2种划分方案,对于合法的方案进行递归操作。用个map对状态进行记忆化即可。
本质不同的状态数大约只有O(|s|+logk)级别,可以通过此题。
#include<bits/stdc++.h>
using namespace std;
#define gc getchar()
#define pc putchar
#define li long long
int t;
string s;
li k;
#define psi pair<string,li>
#define fi first
#define se second
#define mp make_pair
map<psi,li> mm;
const int mo = 1000000009;
inline li wk(psi p){
if(p.fi.size() == 1){
if(!p.se) return 1;
if(p.se == 1) return 2;
if(p.se == 2) return 3;
}
if(p.fi.size() == 2){
if(!p.se) return 1;
if(p.se == 1) return (p.fi[0] == p.fi[1] ? 1 : 2);
}
if(p.fi.size() == 3 && !p.se) return (p.fi[0] == p.fi[1] && p.fi[1] == p.fi[2] ? 0 : 1);
if(mm.find(p) != mm.end()) return mm[p];
li as = 0;
int i;
bool fg = 1;
string nxt;
nxt.clear();
for(i = 0;i < p.fi.size();i += 2){
if(i == p.fi.size() - 1) nxt += p.fi[i];
else if(p.fi[i] == p.fi[i + 1]){
fg = 0;break;
}
else nxt += p.fi[i];
}
if(fg) as += wk(mp(nxt,(p.fi.size() % 2 ? (p.se >> 1) : (p.se + 1 >> 1))));
fg = 1;nxt.clear();
nxt += (p.fi[0] ^ 1);
for(i = 1;i < p.fi.size();i += 2){
if(i == p.fi.size() - 1) nxt += p.fi[i];
else if(p.fi[i] == p.fi[i + 1]){
fg = 0;break;
}
else nxt += p.fi[i];
}
if(fg) as += wk(mp(nxt,(p.fi.size() % 2 ? (p.se + 1 >> 1) : (p.se >> 1))));
return mm[p] = as;
}
int main(){
cin>>t;
while(t--){
cin>>s>>k;
cout<<wk(mp(s,k)) % mo<<endl;
}
return 0;
}