ARC#058F Iroha Loves Strings(贪心+字符串处理+dp预处理)

题面在这里
这题网上找不到题解啊。。于是我就自己对着某大佬的ac代码看了inf小时后终于(假装)懂了。。

题意

小C有 N N 个字符串 s1,s2,s3,...,sN s 1 , s 2 , s 3 , . . . , s N ,并且他准备选择一些字符串顺次连接起来。问所有能得到的字符串中长度为 K K 的字典序最小的。 N2000,K104 N ≤ 2000 , K ≤ 10 4
可以得到 s1s3 s 1 s 3 ,不能得到 s3s1 s 3 s 1

做法

一个贪心的思想:
每次保存一个长度最长的串,并且字典序最小。这个字典序的比较不包括长度。也就是说,如果有两个串存在真包含关系,就要取长度较大的那个串。
每次加进来一个串s,都在原先的串中取一个前缀,将s拼接上去。在所有可能的拼接方式中选取最优的。
但是有一个限制是长度需要为 k k ,所以一个想法就是先dp预处理出i~n串中能拼出的长度,每次只取合法的方案,这样就能保证最终拼出的一定是长为 k k 的。
另外比较字典序的时候,需要求两个串的lcp长度,然后比较lcp+1位的地方的大小。故需要用到exkmp。
具体见代码注释~感觉这题有些地方需要自己理解感受下。。

代码

/*
*   预处理;
*   贪心思想;
*   字符串字典序比较转化为求lcp;
*   各种细节;
*/
#include
#define rep(i, x, y) for(int i = (x); i <= (y); i++)
#define per(i, x, y) for(int i = (x); i >= (y); i--)
#define N 2005
#define M 10005
#define ll long long
using namespace std;
int n, m, len, tp, tot, a[N], q[M], f[M<<1];
char s[N][M], s1[M<<1], ch[M];
bool fl[N][M], ok[M], vis[M];
struct node {
    int x, y; node() {}//x,y表示,将ch中的长为x的串和a[i]拼起来
    node(int a, int b) { x = a, y = b; }
} b[M];
inline void exkmp(char s[], int n) {
    int k = 1; memset(f, 0, sizeof f); f[1] = n;//这里必须清零,或者写成f[1] = 0;
    rep(i, 2, n) {
        f[i] = max(0, min(k+f[k]-i, f[i-k+1]));
        while(i+f[i] <= n && s[f[i]+i] == s[f[i]+1]) f[i]++;
        if(k == 1 || f[i]+i > f[k]+k) k = i;
    }
}
inline int cmp(const node &u, const node &v) {//比较函数,-1/0/1表示,等于相当于存在真包含关系
    if(!u.y && !v.y) return 0;
    if(!u.y) return -cmp(v, u);
    if(!v.y) {
        if(v.x < u.x) return 0;
        int t = f[u.x+u.y+1];
        if(u.x+t >= v.x) return 0;
        return s1[t+1] < s1[u.x+u.y+t+1] ? -1 : 1;
    }
    if(u.x == v.x) return 0;
    if(u.x > v.x) return -cmp(v, u);
    int t = f[u.x+u.y+1];
    if(u.x+t <= v.x) return s1[t+1] < s1[u.x+u.y+t+1] ? -1 : 1;
    t = f[v.x-u.x+1];
    if(v.x+t >= u.x) return 0;
    return s1[v.x-u.x+t+1] < s1[t+1] ? -1 : 1;
}
bool operator < (const node &x, const node &y) { return cmp(x, y) < 0; }
bool operator == (const node &x, const node &y) { return !cmp(x, y); }
int main() {
    scanf("%d%d", &n, &m);//a[i]是第i个串的长度
    rep(i, 1, n) { scanf("%s", s[i]+1); a[i] = strlen(s[i]+1); }
    fl[n+1][0] = 1;//fl[i][j]表示i到n的串中是否能拼出长度为j的串
    per(i, n, 1) rep(j, 0, m) {
        fl[i][j] = fl[i+1][j];//不选第i个串
        if(j >= a[i]) fl[i][j] |= fl[i+1][j-a[i]];//选第i个串
    }
    //ch保存目前为止【不考虑长度的字典序最小】且【最长】的串,并且之后是能拼出长度m的串的
    //ok[i]=1表示这是一个断点,len保存长度
    len = 0; ok[0] = 1;
    rep(i, 1, n) {
        if(!len) {
            if(fl[i+1][m-a[i]]) rep(j, 1, a[i]) ch[++len] = s[i][j];
            ok[len] = 1; continue;
        }
        //将当前串s[i]和目前最优串ch拼起来,exkmp预处理出任意一个后缀与该串的lcp,用f保存
        tot = 0;
        rep(j, 1, a[i]) s1[++tot] = s[i][j];//s1保存拼接后的串
        rep(j, 1, len) s1[++tot] = ch[j];
        exkmp(s1, tot);
        memset(vis, 0, sizeof vis);
        rep(j, 0, m) if(fl[i+1][m-j]) {//枚举前i个串能拼成的合法长度
            if(ok[j]) { vis[j] = 1; b[j] = node(j, 0); }
            if(j >= a[i] && ok[j-a[i]]) {
                if(vis[j]) b[j] = min(b[j], node(j-a[i], a[i]));
                else { vis[j] = 1; b[j] = node(j-a[i], a[i]); }
            }
        }
        q[1] = 0; tp = 1;
        rep(j, 1, m) if(vis[j]) {
            while(tp > 1 && b[j] < b[q[tp]]) tp--;
            if(b[j] == b[q[tp]]) q[++tp] = j;
        }//这里的'=='表示是一个真包含关系,都要保存下来,记录断点
        len = q[tp];
        if(b[len].y) rep(j, 1, a[i]) ch[b[len].x+j] = s[i][j];
        ch[len+1] = 0;
        memset(ok, 0, sizeof ok);
        while(tp) ok[q[tp--]] = 1;
    }//由于每次操作都保证了之后的串能拼出长度为m的,所以最后的答案长一定为m
    printf("%s\n", ch+1);
    return 0;
}

你可能感兴趣的:(AtCoder,贪心,DP,字符串相关)