CF1550E Stringforces
题意:
设 s 是一个由前 k 个小写字母构成的字符串,v 是前 k 个小写字母中的某一个。定义MaxLen(s,v)\mathrm{MaxLen}(s,v)MaxLen(s,v) 表示 s 所有仅由字母 v 构成的连续子串的最长长度。
定义 s 的价值为所有 MaxLen(s,v)\mathrm{MaxLen}(s,v)MaxLen(s,v) 的最小值,其中 v 取遍前 k 个小写字母。
现在给定一个长度为 n 的字符串 s,s 中字母要么是前 k 个小写字母中的某一个,要么是问号。你需要将 s 中的每一个问号替换成前 k 个小写字母中的一个,并最大化 s 的价值。方便起见,你只需要输出这个最大的价值即可。
保证 1≤n≤2×105,1≤k≤171\leq n\leq 2\times 10^5 ,1\leq k\leq 171≤n≤2×105,1≤k≤17。
题解:
又是一个状压dp与线性dp的结合
k<=17,求最大化的MaxLen(s,v)的最小值
不难想到是状压dp+二分,然后就卡住了
二分答案,然后判断mid是否成立,mid成立的条件是:要满足所有的MaxLen(s,v)\mathrm{MaxLen}(s,v)MaxLen(s,v)大于等于mid。那我们就在原串中找是否存在一个长度为mid的全部由第i个字符构成的连续子串(i∈k),且这k个区间互不相交
然后就可以再check中利用状压dp判断mid,设dp[i]表示已经解决需求的字符种类的状态为x时,所选区间的最右端点的最小值。很显然当dp[i]<=n是合法的,说明可以取到这样的区间
对于dp的转移,我们需要知道指定字符在之后区间出现的位置,这样才知道是否可以转移,所有我们设数组pos[i][j]pos[i][j]pos[i][j]表示区间[j,n]匹配字符i的最早位置,预处理出pos,这样之后就可以O(1)转移
状态转移方程为:
dp[x]=min(1<<i)∈xpos[i][dp[x−(1<<i)]+1]dp[x]=min_{(1<<i)∈x}pos[i][dp[x-(1<<i)]+1]dp[x]=min(1<<i)∈xpos[i][dp[x−(1<<i)]+1]
含义就是:状态x可以由状态x-(1<<i),然后在区间[dp[x−(1<<i)]+1,n][dp[x-(1<<i)]+1,n][dp[x−(1<<i)]+1,n]中选择字符i转移得到
代码:
#include <bits/stdc++.h>
#include <unordered_map>
#define debug(a, b) printf("%s = %d\n", a, b);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
clock_t startTime, endTime;
//Fe~Jozky
const ll INF_ll= 1e18;
const int INF_int= 0x3f3f3f3f;
void read(){};
template <typename _Tp, typename... _Tps> void read(_Tp& x, _Tps&... Ar)
{x= 0;char c= getchar();bool flag= 0;while (c < '0' || c > '9')flag|= (c == '-'), c= getchar();while (c >= '0' && c <= '9')x= (x << 3) + (x << 1) + (c ^ 48), c= getchar();if (flag)x= -x;read(Ar...);
}
template <typename T> inline void write(T x)
{if (x < 0) {x= ~(x - 1);putchar('-');}if (x > 9)write(x / 10);putchar(x % 10 + '0');
}
void rd_test()
{
#ifdef ONLINE_JUDGE
#elsestartTime= clock();freopen("data.in", "r", stdin);
#endif
}
void Time_test()
{
#ifdef ONLINE_JUDGE
#elseendTime= clock();printf("\nRun Time:%lfs\n", (double)(endTime - startTime) / CLOCKS_PER_SEC);
#endif
}
const int maxn= 2e5 + 9;
int n, k;
const int mx= (1 << 18) + 9;
int dp[mx];
char s[maxn];
int pos[20][mx];
bool check(int mid)
{//先预处理出pos[][]for (int i= 0; i < k; i++) {int num= 0;for (int j= n; j >= 1; j--) {if (s[j] == 'a' + i || s[j] == '?') //如果是第k个字符或者是?num++; //符合我们要求,连续elsenum= 0; //否则中断if (num >= mid) //如果连续长度大于等于midpos[i][j]= j + mid - 1; //记录最左侧情况else //如果小于midpos[i][j]= pos[i][j + 1];}}memset(dp, INF_int, sizeof(dp));dp[0]= 0;for (int i= 1; i < (1 << k); i++) { //枚举状态for (int j= 0; j < k; j++) { //对于每个字符考虑if ((i >> j) & 1) { //如果当前状态有这个字符if (dp[i - (1 << j)] != INF_int) { //如果去掉这个字符的状态存在if (pos[j][dp[i - (1 << j)] + 1]) { //如果后面的区间内有字符jdp[i]= min(dp[i], pos[j][dp[i - (1 << j)] + 1]); //最小化所有区间右端点}}}}}return dp[(1 << k) - 1] <= n;
}
int main()
{//rd_test();read(n, k);cin >> (s + 1);int l= 1, r= n / k;int res= 0;while (l <= r) {int mid= l + r >> 1;if (check(mid)) {res= mid;l= mid + 1;}elser= mid - 1;}cout << res << endl;//Time_test();
}