并不对劲的loj3115:p5362:[SDOI2019]连续子序列

题目大意

有一个无限长的01串\(T\)满足:
n=0时,\(T_n=0\);n为偶数时,\(T_n=T_{\frac{n}{2}}\);n为奇数时,\(T_n=1-T_{\frac{n-1}{2}}\)
该串的前几位:01101001100101101001011001101001
多组询问,每次询问给定01串\(S\)和长度\(k\),问对于所有在\(T\)里出现的和\(S\)一样的子串,它们后面紧接的\(k\)位有多少种不同的01串。
\(询问数\leq 100;|S|\leq 100;k\leq 10^{18};\)

题解

T这个01串相当于是:
一开串有个0,然后把0换为01变成01,把0换为01把1换为10变成0110,同样的变换变为01101001,变为0110100110010110。
这样转化有个好处就是可以反着来,可以考虑把\(S\)串长度减半。
可以把\(S\)串每相邻两个分一组,按10->1,01->0的规则逆变换;或者在\(S\)最前面在加一个0或1,在按这个规则逆变换。
发现当分到同一组的存在00或11时,变换就是不合法的。
对于任何一个长度大于等于6的串,它其中只要出现连续三次01交替的,逆变换一次就会出现000或111,怎么分都不合法;如果没有出现连续三次01交替,就一定有1001或0110,它们都只有1中逆变换方法。
对于长度为4或5的串,如果有连续两次01交替,逆变换一次后出现00或11,00或11只有一种逆变换方法;如果没有,同上。
所以长度大于3的串的合法的逆变换方法不超过1中。可以只对长度不超过3的串搜索,并手算出长度为1或2的串的一部分比较好算的答案。
在对\(S\)进行逆变换时,同时也要对后\(k\)位逆变换:如果\(S[|S]-1]\)没和\(S[|S|]\)分到一组,为了使逆变换合法,相当于是\(S\)后的第一位已经确定了,所以\(k\)的长度逆变换后是\(\lfloor\frac{k}{2}\rfloor\);反之,长度为\(k\)的串能分\(\lceil\frac{k}{2}\rceil\)组,\(k\)逆变换后是\(\lceil\frac{k}{2}\rceil\)
在经过几次逆变换后\(S\)的长度会变成1,但因为\(k\)极大,可能还会再变换几次才变成手算出的那部分的大小。这样时间看上去是\(2^{log_2\space 10^{18}}=10^{18}\)级别的。
但是发现\(k\)递归时会变成\(\lfloor\frac{k}{2}\rfloor\)\(\lceil\frac{k}{2}\rceil\),也就是说搜到的不同的\(k\)的值数量是在\(log_2\space k\)级别的。可以记忆化搜索。

代码
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
#define psl pair<string,LL>
#define LL long long
#define fi first
#define se second
#define mp make_pair
using namespace std;
LL read()
{
	LL x=0,f=1;char ch=getchar();
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x*f;
}
void write(int x)
{
	if(x==0){putchar('0'),putchar('\n');return;}
	int f=0;char ch[20];
	if(x<0)putchar('-'),x=-x;
	while(x)ch[++f]=x%10+'0',x/=10;
	while(f)putchar(ch[f--]);
	putchar('\n');
	return;
}
const int mod=1e9+9;
int t;
LL n;
string s,nxts;
map<psl,int>M;
int mo(int x){x%=mod;if(!x)x=mod;return x;} 
int work(psl x)
{
	//cout<<x.fi<<" "<<x.se<<endl;
	if(x.fi.size()==1){if(x.se<=2)return x.se+1;}
	if(x.fi.size()==2)
	{
		if(x.se==0)return 1;
		if(x.se==1)return (x.fi[0]==x.fi[1])?1:2;
	}
	if(x.fi.size()==3)
	{
		if(x.fi[0]==x.fi[1]&&x.fi[1]==x.fi[2])return 0;
		if(x.se==0)return 1;
	}
	if(M[x]){return M[x];}
	nxts.clear();int li=x.fi.size()-1,no=0,res=0;
	for(int i=0;i<=li;i+=2)
	{
		if(i+1<=li&&x.fi[i]==x.fi[i+1]){no=1;break;}
		else nxts+=x.fi[i];
	}
	if(!no){res=work(mp(nxts,(x.fi.size()&1)?(x.se>>1ll):(x.se+1ll>>1ll)));}
	nxts.clear();no=0;
	nxts+=(x.fi[0]=='1')?'0':'1';
	for(int i=1;i<=li;i+=2)
	{
		if(i+1<=li&&x.fi[i]==x.fi[i+1]){no=1;break;}
		else nxts+=x.fi[i];
	}
	if(!no){res+=work(mp(nxts,(x.fi.size()&1)?(x.se+1ll>>1ll):(x.se>>1ll)));}
	return M[x]=mo(res);
}
int main()
{
	t=read();
	while(t--)
	{
		cin>>s;n=read();
		write(work(mp(s,n))%mod);
	}
	return 0;
}
奇怪的思路

\(T_i=(i的二进制数位和)mod\space 2\) 。然而毫无用处。

posted @ 2020-06-02 22:10  echo6342  阅读(176)  评论(0编辑  收藏  举报