1. 程式人生 > 其它 >2021暑假 HDU中超 第四場 1004

2021暑假 HDU中超 第四場 1004

2021暑假 HDU中超 第四場 1004 Display Substring

2021暑假 HDU中超 第四場 1004

Display Substring

Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 1055 Accepted Submission(s): 212

題意

給一個字串,告知每個小寫字母對應的價值,求價值和第k名的子串

Sample Input

2
5 5
ababc
3 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
5 15
ababc
3 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

Sample Output

4
-1

考場思路:

求第k小子串,那麼構建字尾自動機

根據傳統演算法搞(記錄SAM上每個節點出發能到達的節點數),突然意識到不對勁

這裡的第k小是指權值和第k小,而不是字典序

不能像傳統演算法那樣,依據第一位轉移

補題:

題目不要求輸出子串是什麼,只需要輸出第k大子串的權值

可以考慮二分答案,假設 \(w\)​ ​為答案,然後需要check是否有k個子串權值小於等於 \(w\)

先回顧一下SAM的性質:

每個節點代表一個等價類,其父節點包含的最大子串為AAA

則這個節點包含形如 XXXAAA,XXAAA,XAAA的子串

我們要計算每個節點中有多少子串的權值是大於 \(w\)

​ 的,並且同一個節點中的子串都連續

權值和顯然單調,可以對每個節點再二分,利用字首和計算答案

check複雜度 \(O(nlogn)\)​ ,總複雜度 \(O(nlognlogn)\)​ ​​

#include <bits/stdc++.h>
#include <vector>
using namespace std;
#define ll long long
#define fff(x,y,z) for(int x=y;x<=z;x++)
#ifdef ACM_LOCAL
const int maxn = 100005;
#else
const int maxn = 100005;
#endif
int mode=1e9+7;
void swap(int &a, int &b){int ins=a;a=b;b=ins;}

struct node{
    int ch[29];
    int fa, len, cnt;
    int fir;
    void clear(){
        for(int i=0;i<28;i++) ch[i] = 0;
        fa = len = fir = 0;
    }
}sam[2*maxn];

int n; ll k;
string s;
int tot, lst;
int val[28], sum[maxn];

void expend(int c){
    int cur = ++tot;
    int p = lst;
    lst = cur;
    sam[cur].len = sam[p].len+1;
    sam[cur].cnt = 1;
    sam[cur].fir = sam[cur].len;

    while(!sam[p].ch[c]){
        sam[p].ch[c] = cur;
        p = sam[p].fa;
        if(p == -1){
            sam[cur].fa = 0;
            return;
        }
    }

    int q = sam[p].ch[c];
    if(sam[p].len+1 == sam[q].len){
        sam[cur].fa = q;
    }else{
        int clo = ++tot;
        sam[clo] = sam[q];
        sam[clo].len = sam[p].len+1;
        sam[clo].fir = sam[q].fir;
        sam[clo].cnt = 0;
        sam[q].fa = clo;
        sam[cur].fa = clo;
        while(p != -1 && sam[p].ch[c] == q){
            sam[p].ch[c] = clo;
            p = sam[p].fa;
        }
    }
}

void init(){
    for(int i=0;i<n*2+5;i++) sam[i].clear();
    sam[0].len = 0;
    sam[0].fa = -1;
    sam[0].cnt = 0;
    tot = 0;
    lst = 0;
}

bool check(int w, ll k){
    //if (numof x<=w) >= k
    ll res = 0;
    for(int i=1;i<=tot;i++){
        int fs = sam[i].fir;//endpos
        int l = fs - sam[i].len + 1, r = fs - sam[sam[i].fa].len, mid;
        while(l < r){
            mid = (l+r)/2;
            if(sum[fs] - sum[mid-1] <= w){
                r = mid;
            }else{
                l = mid+1;
            }
        }
        if(sum[fs] - sum[l-1] <= w) res += fs - sam[sam[i].fa].len - l + 1;
    }
    return res >= k;
}

void solve(){
    cin >> n >> k;
    cin >> s;
    for(int i=0;i<26;i++) cin >> val[i];
    for(int i=1;i<=n;i++) sum[i] = sum[i-1] + val[s[i-1] - 'a'];

    init();
    for(int i=0;i<n;i++) expend(s[i] - 'a');

    int l = 0, r = sum[n], mid;
    while(l < r){
        mid = (l+r)/2;
        if(check(mid, k)){
            r = mid;
        }else{
            l = mid+1;
        }
    }
    if(check(l, k)) cout << l << '\n';
    else cout << "-1\n";

}

signed main() {
#ifdef ACM_LOCAL
    freopen("x.txt","r",stdin);
#endif

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}