AtCoder AGC020 E-Encoding Subsets

题目链接

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|)\) 表示前面的那个与和,则有:

\[f(S)=(1+S_1)f(S_{2...|S|})+\sum_{|P|=1}^{|S|}\sum_{K=2}^{\lfloor\frac{|S|}{|P|}\rfloor}f(w(S,K,|P|))\cdot f(S_{K|P|+1...|S|}) \]

记号和官方题解做到了高度统一(不过他把 \(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;
}
posted @ 2021-08-16 13:50  Neal_lee  阅读(63)  评论(0编辑  收藏  举报