你才会停止|

ricky_lin

园龄:3年8个月粉丝:11关注:2

【题解】CF1550E Stringforces

标签:DP B+

阅读须知:本题解较为详细地讲述的该题解法的思路和来龙去脉,但篇幅较长,请耐心阅读。


Step 1 从题面获取信息

我们考虑,因为最大值最小,所以我们首先想到二分答案。

然后我们又看到 k17 这个限制,所以会想到可能是关于一个 2k 之类的复杂度。

以上就是我们第一步从题目的数据范围中挖掘出来的一些思路。


Step 2 从暴力入手进行优化

我们二分答案 mid 之后,就只需要判断答案 mid 的可行性了,也就是说我们要判断是否每一种字母都能有 mid 长度的子串。

对于判断正确性显然有一种非常简单的暴力,就是枚举字母所确定的子串所出现的相对顺序,然后暴力 check 是否可行,这是 O(n×k!) 的,但是显然不能通过这道题。

① 状态枚举的优化

这时,我们复杂度瓶颈在于花去太多时间在出现顺序的枚举(n!),而我们上文分析到的 2k 显然他的一个很好的转化的终点。

那么为什么能转化呢,又如何转化呢?

为什么能:我们发现每次加进去一种字母的长为 mid 的段时,并不需要知道之前的字母的出现的顺序到底是什么,而我们的 n! 的暴力算法的顺序枚举显然会多出许多不必要的信息的枚举,这显然给了我们代码时间复杂度的优化空间。

如何转化:我们可以设 dpS 表示让集合 S 中的所有字母都满足有长度为 mid 的子串的最短的前缀(显然前缀越短剩下的字母放完的可能性才更大)。

温馨提示:这里的前缀串在记录时为开区间

这下我们就将状态个数优化到了 2kO((n+k)2klogn) 是不能通过本题的,但是状态显然是已经优化到了极致,所以我们只能从转移上下手,尝试把 n 给干掉。


② 转移的优化

我们发现每个位置的转移的显然不需要依赖于当前的整个状态(数量:2k),只需要依赖于当前需要加入哪种字母(数量:k)进入集合,所以说我们可以将原本的 n2k 的转移变成 nk

显然地,你需要预处理转移数组 jumpi,j 表示原来的前缀串以 i 结束,满足入第 j 种字母的要求后,新的前缀串的最小的右端点的位置,这时需要倒序枚举(后面有解释),转移如下:

jumpi,j={i+lenik<i+lensj=a+j || sj=?jumpi+1,jotherwise

而对于第一种转移的限制条件的判定,我们可以动态维护除了这一种字母、其他字母最左边出现的位置,这显然需要倒序枚举。

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);
}
posted @   ricky_lin  阅读(39)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 有我 周深
有我 - 周深
00:00 / 00:00
An audio error has occurred.

作词 : 唐恬/闫光宇

作曲 : 钱雷

编曲 : 赵兆/付虹宇

制作人 : 赵兆

出品 : 共青团中央宣传部

版权 : 中国青少年新媒体协会

制作单位 : 能量悦动音乐

发行单位 : 银河方舟StarNation

出品人 : 郭峰

总监制 : 汤杰

总策划 : 钟亚楠

总统筹 : 金慧子

音乐监制 : 李天鹏/李三木

制作执行 : 张不贰/高聪怡

项目宣发 : 肖健/张国党/孙小千/戴胤/孙雯璟

音乐推广 : 代诗琪/杜思潮/马越/程铁峰/傅之豪

钢琴 : 赵兆

吉他 : 伍凌枫

贝斯 : 韩阳

鼓 : 武勇恒

合唱设计 : 赵兆

合唱 : 凡尔赛合唱团

人声录音 : 耿潇微

人声录音室 : 55TEC Studio Beijing

配唱 : 徐威@52Hz Studio (Shanghai)

混音 : 李游(小骷髅)@55TEC Studio Beijing

海报 : 格子

特别鸣谢 : 周深工作室

世界问 你是谁 来自哪 请回答

爱什么 梦什么 去何方 请回答

答案有 一百年的时光

我来自 硝烟中 课桌旁 的太阳

我来自 硝烟中 课桌旁 的太阳

他和她 宣的誓 迎的仗

来自那 燃烧的 和我一样 的年华

来自世间 一对平凡的夫妻 身旁

来自世间 一对平凡的夫妻 身旁

来自昨天 谁以青春赴万丈 理想

我是寸土 不让的 家乡啊

我是绝不 低头的 倔强啊

接过万千热血 的初衷

当有对答世界 的音量

要怎么形容明天 像我一样

要怎么形容明天 像我一样

承风骨亦有锋芒 有梦则刚

去何方 去最高 的想象

前往皓月星辰 初心不忘

那未来如何登场 有我担当

那未来如何登场 有我担当

定是你只能叫好 那种辉光

护身旁 战远方 有我啊

我的名字就是 站立的地方

Wu~

我的样子 就是 明天的模样

我是朝阳 落在乡间听书声 朗朗

我是朝阳 落在乡间听书声 朗朗

我是屏障 为谁挡一程厄运 的墙

我要一生 清澈地 爱着啊

我要长歌 领着风 踏着浪

朝着星辰大海 的方向

当有对答世界 的音量

要怎么形容明天 像我一样

要怎么形容明天 像我一样

承风骨亦有锋芒 有梦则刚

去远方 去最高 的想象

前往皓月星辰 初心不忘

那未来如何登场 有我担当

那未来如何登场 有我担当

定是你只能叫好 那种辉光

护身旁 战远方 有我啊

一生骄傲为我 站立的地方

Wu~

我的样子 就是 中国的模样

Wu~~~ Wu~~~

当炬火 去化作那道光

“谨以此歌献给一代代不负时代重托的中国青年”

“谨以此歌献给一代代不负时代重托的中国青年”