Loading

P5362 [SDOI2019]连续子序列

Solution

好智慧的思维题啊。

考虑 \(\texttt{M.T.}\) 序列的各种构造方式:

  1. 将原串翻转之后接在后面;
  2. \(i\) 位表示 \(i\) 在二进制中 \(1\) 的个数;
  3. 每次将 1 \(\to\) 100 \(\to\) 01

考虑在本题中,由于我们不知道串具体在什么位置,所以采用方式 \(3\) 来思考。

首先 \(k\) 的范围这么大,肯定是考虑 \(O(\log k)\) 来做。手玩样例,发现 1001 3 的答案和 10 2 的答案好像是一样的。考虑这是为什么。我们把 10 2 的所有情况列举出来:101010011011。然后我们按照方式 \(3\) 扩展一手:100110011001011010011010。不难发现,前 \(4\) 个字符都是 1001,也就是 10 扩展一步得到的。于是我们令 \(T\) 表示 \(S\) 扩展一步得到的结果,然后 \(f(S,k)\) 表示答案,应该有:

\[f(T,k)=f(S,\left\lceil\dfrac{k}{2}\right\rceil) \]

所以,如果我们能够从 \(T\) 得到 \(S\),那么我们每一次转化都可以将 \(k\) 的规模减半。

那如何从 \(T\) 得到 \(S\) 呢?分讨如下:

  1. 如果 \(|T|\) 是偶数:
    • \(T_1T_2,T_3T_4,\cdots,T_{n-1}T_n\),然后缩起来变成 \(S\),期间如果出现 00 一组或者 11 一组的,就不合法;
    • \(T_1,T_2T_3,\cdots,T_{n-2}T_{n-1},T_n\),然后缩起来变成 \(S\),同样有如上条件。此时需要注意,\(T_n\) 应该和后面加的第一个字符一组,所以 \(k\) 得先减一。然后第一个字符需要反转。
  2. 如果 \(|T|\) 是奇数:
    • \(T_1T_2,\cdots,T_{n-2}T_{n-1},T_n\),然后缩起来,此时 \(k\) 需要减一。
    • \(T_1,T_2T_3,\cdots,T_{n-1}T_n\),然后缩起来,此时第一个字符需要反转。

仔细思考上面四种情况。我们在拿到一个字符串 \(S\) 的时候,根据它不同的奇偶性,最多会有 \(2\) 种方案。直接记忆化递归就行了。由于每次递归时 \(k\) 的规模减半,不难发现,当 \(S\) 还很长的时候,两种方案不能同时满足,所以单次是 \(O(|S|+\log k)\) 的。

注意边界情况。考虑到当 \(|S|+k\le 3\) 的时候,不能完全按照上面的方式去转化。比如说,现在要求 \(f(010,0)\),如果不判掉就会转移到 \(f(00,0)+f(11,0)=2\),显然不合法。主要原因就是最后一个字符后面明明没有与之组合却在我们的臆想下转移到了 \(f(00,0)\),但是可巧的是,在 \(|S|+k>3\) 的情况下这样转移一定不合法,所以直接特判就可以了。

Code

// Problem: 
//     P5362 [SDOI2019]连续子序列
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5362
// Memory Limit: 500 MB
// Time Limit: 1000 ms

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb emplace_back
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define all(a) a.begin(),a.end()
#define siz(a) (int)a.size()
#define clr(a) memset(a,0,sizeof(a))
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
#define pt(a) cerr<<#a<<'='<<a<<' '
#define pts(a) cerr<<#a<<'='<<a<<'\n'
#define int long long
using namespace std;
const int MOD=1e9+9;
map<pair<string,int>,int> f;
int calc(string s,int k){
	if(siz(s)==1){
		if(k==0) return 1;
		if(k==1) return 2;
		if(k==2) return 3;
	}if(siz(s)==2){
		if(k==0) return 1;
		if(k==1) return 1+(s[0]!=s[1]);
	}if(siz(s)==3){
		if(k==0) return !(s[0]==s[1]&&s[1]==s[2]);
	}
	if(f.count(mkp(s,k))) return f[mkp(s,k)];
	string t="";
	bool flag=1;
	int ans=0;
	for(int i=0;i<siz(s);i+=2){
		if(i+1<siz(s)&&s[i]==s[i+1]){flag=0;break;}
		t+=s[i];
	}if(flag) ans+=calc(t,siz(s)&1?k>>1:(k+1)>>1);
	t=s[0]^1;flag=1;
	for(int i=1;i<siz(s);i+=2){
		if(i+1<siz(s)&&s[i]==s[i+1]){flag=0;break;}
		t+=s[i];
	}if(flag) ans+=calc(t,siz(s)&1?(k+1)>>1:k>>1);
	ans%=MOD;
	return f[mkp(s,k)]=ans;
}
void solve(){
	string s;int k;cin>>s>>k;
	cout<<calc(s,k)<<'\n';
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T;for(cin>>T;T--;)solve();
	return 0;
}
posted @ 2022-11-02 15:39  ZCETHAN  阅读(30)  评论(0编辑  收藏  举报