TonyYin's Blog

Back

题目链接:P3426 [POI2005]SZA-Template - 洛谷 - 计算机科学教育新生态

dp[i]\operatorname{dp}[i] 表示前缀 [1,i][1, i] 的答案,设 lst[i]\operatorname{lst}[i] 表示以前缀 [1,i][1, i] 为印章,最远能覆盖到的位置。

容易发现,前缀 [1,i][1, i] 一定能被自己覆盖,因此 dp[i]\operatorname{dp}[i] 最大为 ii

其何时能被更短的段覆盖?

考虑其 border\rm{border},我们以覆盖其 border\rm{border} 相同的方式,也就是以 dp[border[i]]\operatorname{dp}[\operatorname{border}[i]] 为印章长度,尝试覆盖 [1,i][1, i]

如果可行,那么 dp[i]=dp[border[i]]\operatorname{dp}[i]=\operatorname{dp}[\operatorname{border}[i]]

判断可行?

可行当且仅当:lst[dp[border[i]]]>=iborder[i]\operatorname{lst}[\operatorname{dp}[\operatorname{border}[i]]] >= i - \operatorname{border}[i].

意思就是,以 dp[border[i]]\operatorname{dp}[\operatorname{border}[i]] 为印章长度,至少要可以覆盖到 iborder[i]i - \operatorname{border}[i]

[iborder[i],i][i - \operatorname{border}[i], i] 这段与 [1,border[i]][1, \operatorname{border}[i]] 完全相同,一定能被覆盖,所以只需 [iborder[i]][i-\operatorname{border}[i]] 之前能被覆盖。

#include <bits/stdc++.h>
using namespace std;
const int MAXLEN  = 5e5 + 10;
char s[MAXLEN];
int len, nxt[MAXLEN];
void get_border() {
    nxt[1] = 0;
    for(int i = 2; i <= len; i++) {
        int match = nxt[i - 1];
        while(s[i] != s[match + 1] && match != 0) match = nxt[match];
        if(s[i] == s[match + 1]) match++;
        nxt[i] = match;
    }
}
int dp[MAXLEN]; //dp[i]表示前缀[1, i]的答案
int lst[MAXLEN]; //lst[i]表示以前缀[1, i]为印章,最远能覆盖到的位置
int main() {
    scanf("%s", (s + 1)); len = strlen(s + 1);
    get_border();
    dp[1] = 1; lst[1] = 1;
    for(int i = 2; i <= len; i++) {
        dp[i] = i;
        if(lst[dp[nxt[i]]] >= i - nxt[i]) dp[i] = dp[nxt[i]];
        lst[dp[i]] = i;
    }
    cout << dp[len] << endl;
    return 0;
}
cpp
【洛谷-P3426】[POI2005]SZA-Template
https://www.tonyyin.top/blog/oi-solution/p3426
Author TonyYin
Published at October 19, 2021
Comment seems to stuck. Try to refresh?✨