ARC#058F Iroha Loves Strings(貪心+字符串處理+dp預處理)

題面在這裏
這題網上找不到題解啊。。於是我就自己對着某大佬的ac代碼看了inf小時後終於(假裝)懂了。。

題意

小C有N 個字符串s1,s2,s3,...,sN ,並且他準備選擇一些字符串順次連接起來。問所有能得到的字符串中長度爲K 的字典序最小的。N2000,K104
可以得到s1s3 ,不能得到s3s1

做法

一個貪心的思想:
每次保存一個長度最長的串,並且字典序最小。這個字典序的比較不包括長度。也就是說,如果有兩個串存在真包含關係,就要取長度較大的那個串。
每次加進來一個串s,都在原先的串中取一個前綴,將s拼接上去。在所有可能的拼接方式中選取最優的。
但是有一個限制是長度需要爲k ,所以一個想法就是先dp預處理出i~n串中能拼出的長度,每次只取合法的方案,這樣就能保證最終拼出的一定是長爲k 的。
另外比較字典序的時候,需要求兩個串的lcp長度,然後比較lcp+1位的地方的大小。故需要用到exkmp。
具體見代碼註釋~感覺這題有些地方需要自己理解感受下。。

代碼

/*
*   預處理;
*   貪心思想;
*   字符串字典序比較轉化爲求lcp;
*   各種細節;
*/
#include<bits/stdc++.h>
#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章