CF1550E Stringforces

Posted Jozky86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF1550E Stringforces相关的知识,希望对你有一定的参考价值。

CF1550E Stringforces

题意:

设 s 是一个由前 k 个小写字母构成的字符串,v 是前 k 个小写字母中的某一个。定义 M a x L e n ( s , v ) \\mathrm{MaxLen}(s,v) MaxLen(s,v) 表示 s 所有仅由字母 v 构成的连续子串的最长长度。
定义 s 的价值为所有 M a x L e n ( s , v ) \\mathrm{MaxLen}(s,v) MaxLen(s,v) 的最小值,其中 v 取遍前 k 个小写字母。
现在给定一个长度为 n 的字符串 s,s 中字母要么是前 k 个小写字母中的某一个,要么是问号。你需要将 s 中的每一个问号替换成前 k 个小写字母中的一个,并最大化 s 的价值。方便起见,你只需要输出这个最大的价值即可。
保证 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ k ≤ 17 1\\leq n\\leq 2\\times 10^5 ,1\\leq k\\leq 17 1n2×1051k17

题解:

又是一个状压dp与线性dp的结合
k<=17,求最大化的MaxLen(s,v)的最小值
不难想到是状压dp+二分,然后就卡住了
二分答案,然后判断mid是否成立,mid成立的条件是:要满足所有的 M a x L e n ( 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的转移,我们需要知道指定字符在之后区间出现的位置,这样才知道是否可以转移,所有我们设数组 p o s [ i ] [ j ] pos[i][j] pos[i][j]表示区间[j,n]匹配字符i的最早位置,预处理出pos,这样之后就可以O(1)转移
状态转移方程为:
d p [ x ] = m i n ( 1 < < i ) ∈ x p o s [ i ] [ d p [ 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),然后在区间 [ d p [ 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
#else
    startTime= clock();
    freopen("data.in", "r", stdin);
#endif
}
void Time_test()
{
#ifdef ONLINE_JUDGE
#else
    endTime= 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++; //符合我们要求,连续
            else
                num= 0; //否则中断
            if (num >= mid) //如果连续长度大于等于mid
                pos[i][j]= j + mid - 1; //记录最左侧情况
            else //如果小于mid
                pos[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]) { //如果后面的区间内有字符j
                        dp[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;
        }
        else
            r= mid - 1;
    }
    cout << res << endl;
    //Time_test();
}

以上是关于CF1550E Stringforces的主要内容,如果未能解决你的问题,请参考以下文章

Educational Codeforces Round 111 (Rated for Div. 2) E. Stringforces 二分答案+状压dp

如何从后台弹出片段

cf 模拟

CF1435 游记

无法解析符号 c882c94be45fff9d16a1cf845fc16ec5

本人想学习破解技术但是看不懂反汇编代码!求助!!