【题解】CF1550E Stringforces
标签:DP
阅读须知:本题解较为详细地讲述的该题解法的思路和来龙去脉,但篇幅较长,请耐心阅读。
Step 1 从题面获取信息
我们考虑,因为最大值最小,所以我们首先想到二分答案。
然后我们又看到
以上就是我们第一步从题目的数据范围中挖掘出来的一些思路。
Step 2 从暴力入手进行优化
我们二分答案
对于判断正确性显然有一种非常简单的暴力,就是枚举字母所确定的子串所出现的相对顺序,然后暴力 check
是否可行,这是
① 状态枚举的优化
这时,我们复杂度瓶颈在于花去太多时间在出现顺序的枚举(
那么为什么能转化呢,又如何转化呢?
为什么能:我们发现每次加进去一种字母的长为
如何转化:我们可以设
温馨提示:这里的前缀串在记录时为开区间
这下我们就将状态个数优化到了
② 转移的优化
我们发现每个位置的转移的显然不需要依赖于当前的整个状态(数量:
显然地,你需要预处理转移数组
而对于第一种转移的限制条件的判定,我们可以动态维护除了这一种字母、其他字母最左边出现的位置,这显然需要倒序枚举。
Step 3 思考实现细节并敲出代码
code:
#include<bits/stdc++.h> using namespace std; const int NN = 2e5 + 8,MM = 20; int dp[1 << MM]; int n,k; char s[NN];//字符串 int jump[NN][MM]; int minp[MM];//第i种字母最左边出现的位置 int res[MM];//除第i种字母以外的其他字母最左边出现的位置 bool solve(int len){ memset(dp,0x3f,sizeof(dp)); memset(res,0x3f,sizeof(res));//初始化 for(int i = 0; i < k; ++i) minp[i] = n+1,jump[n+1][i] = n+2,jump[n+2][i] = n+2;//限定边界 for(int i = n; i >= 1; --i){ if(s[i] != '?'){//更新res minp[s[i]-'a'] = i; for(int j = 0; j < k; ++j){ if(s[i]-'a' == j) continue; res[j] = i; } } for(int j = 0; j < k; ++j){//转移 if(res[j] >= i+len) jump[i][j] = min(n+2,i+len); else jump[i][j] = jump[i+1][j]; } } dp[0] = 1; for(int i = 1; i < (1 << k); ++i){//DP转移 for(int j = 0; j < k; ++j)if(i >> j & 1){ dp[i] = min(jump[dp[i^(1 << j)]][j],dp[i]); } } return dp[(1 << k)-1] <= n+1; } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin >> n >> k; cin >> s+1; int l = 0,r = n,ans = 0; while(l <= r){ int mid = (l + r) / 2; if(solve(mid)) l = mid + 1,ans = mid; else r = mid - 1; } printf("%d",ans); }
本文来自博客园,作者:ricky_lin,转载请注明原文链接:https://www.cnblogs.com/rickylin/p/solution_CF1550E.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步