AtCoder AGC020 E-Encoding Subsets
题目链接
题目大意
给出一个 \(01\) 串 \(S\),对于串中连续出现 \(K\geq 2\) 次的子串 \(P\),可以将 \(PP...P\) 改写成 \((P\times K)\) ,如 001001001
可被改写成 00(1(0x2)x2)1
或 (001x3)
。现在对于所有和 \(S\) 长度相同且 \(S\wedge T=T\) 的串 \(T\),求出改写方案数的总和,答案对 \(10^9+7\) 取模。
\(1\leq |S|\leq 100\)
思路
先考虑对于一个串 \(S\) 如何单独计算答案,这个不难,容易想到用区间 \(DP\) 做。设 \(dp_{i,j}\) 为 \([i,j]\) 内的串的改写方案数,转移时强制区间开头连续的一段是要被改写的即可,通过合适的预处理可以做到 \(O(n^3\log 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;
}