把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5362】[SDOI2019] 连续子序列(找规律)

点此看题面

  • 有一个\(01\)序列\(T\),满足\(T_0=0,T_{2n}=T_n,T_{2n+1}=T_n\oplus1\)
  • 给定一个\(01\)\(S\)和一个常数\(k\),求有多少字符串,满足它是在\(S\)后面加上\(k\)个字符得到的,且在\(T\)中出现过。
  • 数据组数\(\le100\)\(|S|\le100,k\le10^{18}\)

找规律+记忆化搜索

说实话我是先在NOI Online #4的\(T1\)中遭遇大失败之后才来做这道题的,然而依旧是大失败。

这个\(01\)\(T\)有非常多的性质,这里就不一一列举了,只给出这道题中需要的关键性质:

如果我们每次将这个\(01\)串长度倍增,那么相当于每个\(0\)会变成\(01\),每个\(1\)会变成\(10\)

利用这个性质,这道题我们只需要做逆变换,把\(S\)给缩回去即可。

具体地,对于当前串\(S\),它只有两种可能的来源:从第一个字符开始每两个一组,从第二个字符开始每两个一组。

注意,一组的两个字符必须不同,也正因此两侧的单个字符其实也可以唯一确定与其配对的字符。

然后就直接记搜一遍即可,因为一次搜索尽管会产生两个状态,但\(S\)的长度也相应减半了,所以总状态数不大,复杂度是正确的。

注意,当\(S\)长度和\(k\)都很小的时候需要判一下边界,否则会挂掉。比较良心的是,需要判的边界在样例中都有涉及,因此只要过了样例基本上就没问题了。(做的时候我还思考为什么每个样例都要特判,还怀疑自己想法是不是错了。。。)

代码:\(O(T(|S|+logk))\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define LL long long
#define X 1000000009
using namespace std;
string s;LL k;
map<pair<string,LL>,int> f;I int DP(string s,Con LL& k)
{
	if(f.count(make_pair(s,k))) return f[make_pair(s,k)];//记忆化搜索
	RI l=s.length();if(l==1&&k<=2) return k+1;//l=1且k很小的边界状态
	if(l==2&&k<=1) return 1+(k&&s[0]^s[1]);if(l==3&&!k) return s[0]^s[1]||s[1]^s[2];//l=2或3且k很小的边界状态
	RI i,p,t=0;string ns;for(p=0;p<=1;++p)//两种划分
	{
		for(p&&((ns="0")[0]=s[0]^1),i=p;i+1<l&&s[i]^s[i+1];i+=2) ns+=s[i];//确定左侧单字符,中间两两配对字符必须不同
		i+1>=l&&(i==l-1&&(ns+=s[l-1],0),t=(t+DP(ns,k+!(l&1^p)>>1))%X);//确定右侧单字符,递归
	}return f[make_pair(s,k)]=t;
}
int main()
{
	ios::sync_with_stdio(false);RI Tt;cin>>Tt;W(Tt--) cin>>s>>k,cout<<DP(s,k)<<endl;return 0;
}
posted @ 2021-03-31 12:40  TheLostWeak  阅读(62)  评论(0编辑  收藏  举报