P5362 [SDOI2019]连续子序列 思维题

题意:

戳这里

分析:

不会分析 , 直接找规律:

肉眼分析可以发现, \(T.M\) 序列的生成方式有两种:

  1. 取反然后复制一遍 \(0110\to 01101001\)
  2. \(\tiny \color{white}{\text{不}}\)容易发现 \(0110\to 0(1)1(0)1(0)0(1)\)

但是第一个性质不方便用,因为 \(|S|+k\) 不一定是 \(2\) 的整数次幂,所以我们考虑第二种方法

我们发现在后面添加的 \(k\) 的布尔序列也会是 \(01\) ,\(10\) 交替的,所以一定不会存在连续的三个以上的相同字符,所以我们可以反向推导,由第二个性质可以知道 \(0\to 01\) \(1\to 10\) 所以我们每一次可以将 \(S\) 长度减半 \(k\) 也减半,这样递归 \(\log k\) 层就能得到 \(k<=2\) 的情况,然后我们手动判掉 \(n,k<=3\) 的情况,顺便对 \(S,k\) 哈希一下之后记忆化

具体来说就是,每次分两种情况讨论,一种是第一位空出来与前面的拼成一个 \(0\),第二种就是从第一位开始两个一组

代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second
#define psl pair<string,long long>

using namespace std;

namespace zzc
{
    typedef long long ll;
    const ll mod = 1e9+9;
    map<psl,ll> f;
    
    long long solve(string s,long long k)
    {
       if(f[mk(s,k)]) return f[mk(s,k)];
       long long n=s.size(),res=0;
       if(n==1&&k<=2) return k+1;
       if(n==2&&k==0) return 1;
       if(n==2&&k==1) return (s[0]==s[1])?1:2;
       if(n==3&&k==0) return (s[0]!=s[1]||s[1]!=s[2]);
       bool flag=true;
       string nxt;
       for(int i=0;i<n;i+=2)
       {
           if(i==n-1||s[i]!=s[i+1]) nxt+=s[i];
           else {flag=false;break;}
       }
       if(flag) res+=solve(nxt,(n&1)?(k>>1):((k+1)>>1))%mod;
       nxt=((s[0]-'0')^1)+'0';
       flag=true;
       for(int i=1;i<n;i+=2)
       {
           if(i==n-1||s[i]!=s[i+1]) nxt+=s[i];
           else {flag=false;break;}
       }
       if(flag) res+=solve(nxt,(n&1)?((k+1)>>1):(k>>1))%mod;
       return f[mk(s,k)]=res%mod;
    }

	void work()
	{
        ios::sync_with_stdio(false);
        string s;
        ll k,t;
        cin>>t;
        while(t--)
        {
            cin>>s>>k;
            cout<<solve(s,k)%mod<<'\n';
        }
	
	}

}

int main()
{
	zzc::work();
	return 0;
}

posted @ 2021-01-09 16:25  youth518  阅读(71)  评论(0编辑  收藏  举报