AT3859 [AGC020E] Encoding Subsets
作者:互联网
有一道题 也是字符串的压缩,但是要求的是压缩后的最短长度,然后做法是区间 \(\tt DP\)。故考虑区间 \(\tt DP\)(应该比较显然看出)。
我们先把问题弱化:去掉“包含”的条件,对单个字符串求答案。
考虑如刚刚那题,记录 \(f_{l,r}\) 代表一个区间的答案。
但是这一个不够(我考场上在这里挣扎了许久),还要用 \(g_{l,r}\) 代表 不是由多个压缩合并而成的压缩的答案。
转移不难。
- \(f_{l,r}\) 的转移:枚举断点 \(k\in[l,r)\),\(f_{l,r}=\sum g_{l,k}f_{k+1,r}\)。
- 原因:如果使用 \(f\) 来代替这个式子里面的 \(g\),那样会算重。
- \(g_{l,r}\) 的转移:枚举 \(k\in[1,r-l+1]\) 使得 \(S_{l\cdots l+k-1}\) 是 \(S_{l\cdots r}\) 的循环节(即它会重复很多遍),\(g_{l,r}=\sum f_{l,l+k-1}\)。
- 原因:\(g\) 的来源就是每一种可能的循环节情况之和。
考虑原问题。
我们不妨用一个字符串 \(S\) 代替原先的状态 \(\{l,r\}\),代表 \(S\) 的答案。
- \(f_S\) 的转移:类似的套路枚举断点,然后直接
string::substr
找到 \([l,k]\) 和 \((k+1,r]\) 的两个子串。 - \(g_S\) 的转移:有点改变(但是不难想),枚举一个可能的循环的位置 \(k\)(只需要满足 \(k\mid r-l+1\) 即可)然后考虑有一个字符串 \(T\) 作为循环节,对于它的任意一位
- 找到每个 \(T\) 需要覆盖上的字符串 \(T^{\prime}\) 的对应位。
- 如果全部都是 \(1\),那这一位就可以取到 \(1\)(如果有一位是 \(0\),根据“包含”的规则,无法将其改变成 \(1\)。)
- 一定可以取到 \(0\)(如果有一位是 \(1\),根据“包含”的规则,可以将其改变成 \(1\)。)
- 找到对于每个可能的 \(k\mid r-l+1\),对应的循环节字符串 \(T\) 的 \(1\) 的个数 \(x\),那么 \(g_{l,r}=\sum2^xf_T\)。
- 原因:\(2^x\) 可以代表 \(T\) 包含几个字符串(原因:这些位置有两种选择,其它位置有一种选择)
对于状态的存储,考虑记忆化搜索,然后 unordered_map<string,int>
解决。
#import<iostream>
#import<unordered_map>
using namespace std;
using ll = long long;
const ll mod = 998244353;
string st;
unordered_map<string,int> f,g;
ll F(string s);
ll G(string s);
ll F(string s){
if(f.find(s) != f.end()) return f[s];
int sum = 0,len = s.length();
for(int i = 1;i <= len;++i)
(sum += G(s.substr(0,i)) * F(s.substr(i,len - i + 1)) % mod) %= mod;
return f[s] = sum;
}
ll G(string s){
if(g.find(s) != g.end()) return g[s];
int sum = 0,len = s.length();
for(int i = 1;i < len;++i) if(len % i == 0){
string str = "";
for(int p = 0;p < i;++p){
bool f = 1;
for(int q = p;q < len;q += i)
f &= (s[q] - '0');
str += f ? '1' : '0';
}
(sum += F(str)) %= mod;
}
return g[s] = sum;
}
void init(){ f[""] = g[""] = g["0"] = 1; g["1"] = 2; }
int main(){
cin >> st;
init();
cout << F(st) << endl;
return 0;
}
标签:string,Subsets,Encoding,ll,AT3859,枚举,循环,字符串,sum 来源: https://www.cnblogs.com/zeno6174/p/AT3859.html