题目链接
题目大意
给出一个 (01) 串 (S),对于串中连续出现 (Kgeq 2) 次的子串 (P),可以将 (PP...P) 改写成 ((P imes K)) ,如 001001001
可被改写成 00(1(0x2)x2)1
或 (001x3)
。现在对于所有和 (S) 长度相同且 (Swedge T=T) 的串 (T),求出改写方案数的总和,答案对 (10^9+7) 取模。
(1leq |S|leq 100)
思路
先考虑对于一个串 (S) 如何单独计算答案,这个不难,容易想到用区间 (DP) 做。设 (dp_{i,j}) 为 ([i,j]) 内的串的改写方案数,转移时强制区间开头连续的一段是要被改写的即可,通过合适的预处理可以做到 (O(n^3log n)) 。
当你尝试分析如何快速地把答案加起来的时候,会发现当 (S) 有一位发生翻转后,答案会截然不同,两者是没有什么继承关系的,所以分开计算答案没有前途!既然分开来不大能做,那就尝试合起来一起计算答案。
设 (f(S)) 为当串为 (S) 时题目对应的答案,转移还是强制 (S) 开头被改写,注意到现在 (f(S)) 代表了 (S) 所有子集的答案,所以在折叠的时候,需要满足的条件是 (PP...P) 为 (S) 对应前缀的子集,这等价于 (P) 是 (S_{1...|P|}wedge S_{|P|+1...2|P|}wedge...wedge S_{(K-1)|P|+1,K|P|}) 的子集,而这个刚好存在此与和的 (f) 里面,所以直接用 (f) 值转移即可。实际转移时还需考虑开头不折叠的情况,设 (w(S,K,|P|)) 表示前面的那个与和,则有:
记号和官方题解做到了高度统一(不过他把 (K=2) 写成了 (K=1))
这里时间复杂度看起来上限是 (O(2^{|S|+1})) 的,感觉这个题最重要的地方就是,你要看出来这个做法其实是 (O()能过()) 的,进而分析出其真正的复杂度,而不是被假上限给吓跑了。
注意到所有会被用到的 (T) 都是由原串 (S) 导出来的,可以很直观地感受到长度比较长的串其实非常少,比如长度 (>frac{|S|}{2}) 的串只有 (O(n)) 个,如果允许折叠一下,那么长度 (>frac{n}{4}) 的串有 (O(n^2)) 个,这样平衡一下,可以发现折两次的时候时间复杂度上限降到最低,(3) 种折法可以视为常数,则时复为 (O(2^{frac{n}{8}}+n^3)),运算量仅有 (7e5),可以轻松通过此题。
Code
实现的时候用了哈希表配记忆化搜索,自定义哈希的内容来自 (color{black}{n}color{red}{eal}),然而并没有比 (map) 快多少诶。
#include<iostream>
#include<chrono>
#include<unordered_map>
#include<vector>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 110
#define ll long long
#define mod 998244353
#define modd 1000000007
using namespace std;
struct custom_hash{
static uint64_t splitmix64(uint64_t x){
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator () (uint64_t x) const{
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
size_t operator () (pair<uint64_t, uint64_t> x) const{
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(splitmix64(x.first + FIXED_RANDOM) ^ (x.second + FIXED_RANDOM));
}
};
unordered_map<pair<ll, ll>, int, custom_hash> dp;
pair<ll, ll> val(vector<int> p){
ll ret1 = 1, ret2 = 1;
for(int x : p) ret1 = (ret1*3 + x)%mod, ret2 = (ret2*3 + x)%modd;
return make_pair(ret1, ret2);
}
vector<int> sub(vector<int> p, int l, int r){
return {p.begin()+l, p.begin()+r};
}
int dfs(vector<int> p){
pair<ll, ll> id = val(p); int n = p.size();
if(dp[id]) return dp[id];
dp[id] = (1+p.front()) * dfs(sub(p, 1, n)) % mod;
rep(len,1,n) rep(k,2,n/len){
vector<int> q(len, 1);
rep(i,0,len-1) rep(j,0,k-1) q[i] &= p[j*len+i];
(dp[id] += (ll) dfs(q) * dfs(sub(p, len*k, n)) % mod) %= mod;
}
return dp[id];
}
int main(){
string s; cin>>s;
vector<int> p;
rep(i,0,(int)s.size()-1) p.push_back(s[i]-'0');
dp[make_pair(1, 1)] = 1;
cout<< dfs(p) <<endl;
return 0;
}