2019湖南省大学生程序设计竞赛部分题解

赛场上k题写链表的启发式合并,直接给写跪了,耽误了太多时间,导致最后只出了7题,D题dp给漏了,好菜啊…
C.Distinct Substrings

题意:给一个长度为 n n n的字符串 s s s,定义 h ( i ) h(i) h(i)为在 s s s串末尾添加字符 i i i新增加的本质不同子串数量,求 h ( i ) ∗ 3 i ( 1 < = i < = m ) h(i)*3^{i}(1<=i<=m) h(i)3i(1<=i<=m)的异或和
解法:我们把字符串颠倒一下,问题转化成在 s s s前面添加一个字符,所能增加的本质不同子串数,假设添加了 i i i,求出 s ′ s' s s s s所有子串最长的lcp,显然 h ( i ) = n + 1 − l c p h(i)=n+1-lcp h(i)=n+1lcp,用 e x k m p exkmp exkmp s s s每个后缀与 s s s的最长公共前缀,用 e x [ i ] ex[i] ex[i]表示 s ( i , n − 1 ) s(i,n-1) s(i,n1) s s s的lcp,如果 i ! = 0 i!=0 i!=0,显然我们可以更新: h ( s [ i − 1 ] ) = m a x ( h ( s [ i − 1 ] ) , e x [ i ] + 1 ) h(s[i-1])=max(h(s[i-1]),ex[i]+1) h(s[i1])=max(h(s[i1])ex[i]+1),这样,就解决掉这题难点啦

//其实还可以二分+hash,但是我t了,好像有看到别人这么过了

#include
#define ll long long
using namespace std;
const int maxn = 1e6 + 3, mod = 1e9 + 7;
// 字符串下标从0开始
int nex[maxn], ex[maxn]; //模式串nex,匹配串ex
void get_nex(int *str, int len) {
     
    int i = 0, j, pos;
    nex[0] = len;
    while (str[i] == str[i+1] && i+1 < len) ++i;
    nex[1] = i;
    pos = 1;
    for (int i = 2; i < len; ++i) {
     
        if (nex[i-pos] + i < nex[pos] + pos) nex[i] = nex[i-pos];
        else {
     
            j = nex[pos] + pos - i;
            if (j < 0) j = 0;
            while (i+j < len && str[j] == str[j+i]) ++j;
            nex[i] = j;
            pos = i;
        }
    }
}
void get_ex(int *s1, int *s2, int len1, int len2) {
      // s1匹配s2
    int i = 0, j, pos;
    get_nex(s2, len2);
    while (s1[i] == s2[i] && i < len1 && i < len2) ++i;
    ex[0] = i;
    pos = 0;
    for (int i = 1; i < len1; ++i) {
     
        if (nex[i-pos] + i < ex[pos] + pos) ex[i] = nex[i-pos];
        else {
     
            j = ex[pos] + pos - i;
            if (j < 0) j = 0;
            while (i+j < len1 && j < len2 && s1[i+j] == s2[j]) ++j;
            ex[i] = j;
            pos = i;
        }
    }
}
int n, m, a[maxn], mx[maxn];
void solve() {
     
    for (int i = n - 1; ~i; i--)
        scanf("%d", &a[i]);
    get_ex(a, a, n, n);
    for (int i = 1; i <= m; i++)
        mx[i] = 0;
    for (int i = 0; i < n; i++) {
     
        if (i)
            mx[a[i - 1]] = max(mx[a[i - 1]], ex[i] + 1);
        mx[a[i]] = max(mx[a[i]], 1);
    }
    int ans = 0, cat = 1;
    for (int i = 1; i <= m; i++) {
     
        cat = 1ll * cat * 3 % mod;
        int res = 1ll * (n + 1 - mx[i]) * cat % mod;
        ans ^= res;
    }
    printf("%d\n", ans);
}
int main() {
     
    while (~scanf("%d%d", &n, &m))
        solve();
}

D. Modulo Nine

题意:有一个长度为 n n n的序列,你可以给每个位置填0-9的一个数,有 m m m个限制,每个限制 [ l i , r i ] [l_{i}, r_{i}] [li,ri]要求区间内的数相乘必须为9的倍数,问一共有多少种合法的填数方案。
解法:设 d p [ i ] [ j ] dp[i][j] dp[i][j]为当前已经填过的数中,倒数第二个3的位置在 i i i,最后一个3的位置在 j j j,假设当前在位置 p p p,我填了个0或者9,那么新的状态变成 d p [ p ] [ p ] dp[p][p] dp[p][p],那么 d p [ p ] [ p ] + = d p [ i ] [ j ] ∗ 2 dp[p][p]+=dp[i][j]*2 dp[p][p]+=dp[i][j]2,如果填了个3或者6,那么 d p [ j ] [ p ] + = d p [ i ] [ j ] ∗ 2 dp[j][p]+=dp[i][j]*2 dp[j][p]+=dp[i][j]2,填剩下的数状态不变: d p [ i ] [ j ] + = d p [ i ] [ j ] ∗ 6 dp[i][j]+=dp[i][j]*6 dp[i][j]+=dp[i][j]6,然后找到所有的右端点在 p p p的限制条件区间 [ l k , p ] [l_{k},p] [lk,p],如果该区间只有一个3,显然非法,枚举 l ( l < l k ) l(ll(l<lk),再枚举 r ( l k < = r < = p ) r(l_{k}<=r<=p) r(lk<=r<=p),将 d p [ l ] [ r ] dp[l][r] dp[l][r]设为0即可
#include
#define ll long long
using namespace std;
const int mod = 1e9 + 7;
void add(int &x, int y) {
     
    x += y;
    if (x >= mod)
        x -= mod;
    if (x < 0)
        x += mod;
}
vector<int> G[51];
int d[51][51], P[51], P2[51];
int main() {
     
    int n, m, l, r;
    P[0] = P2[0] = 1;
    for (int i = 1; i <= 50; i++) {
     
        P[i] = 1ll * P[i - 1] * 10 % mod;
        P2[i] = 1ll * P2[i - 1] * 9 % mod;
    }
    while (~scanf("%d%d", &n, &m)) {
     
        for (int i = 0; i <= n; i++) {
     
            G[i].clear();
            for (int j = i; j <= n; j++)
                d[i][j] = 0;
        }
        d[0][0] = 1;
        for (int i = 1; i <= m; i++) {
     
            scanf("%d%d", &l, &r);
            G[r].push_back(l);
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
     
            for (int j = i - 1; ~j; j--)
            for (int k = j; ~k; k--)
                if (d[k][j] != -1) {
     
                    add(d[i][i], 2ll * d[k][j] % mod);
                    add(d[j][i], 2ll * d[k][j] % mod);
                    d[k][j] = 1ll * d[k][j] * 6 % mod;
                }
            for (auto v : G[i]) {
     
                for (int j = 0; j < v; j++)
                    for (int k = j; k <= i; k++)
                        d[j][k] = -1;
            }
        }
        for (int i = 0; i <= n; i++)
            for (int j = i; j <= n; j++)
                if (d[i][j] != -1)
                    add(ans, d[i][j]);
        printf("%d\n", ans);
    }
}

你可能感兴趣的:(动态规划,字符串----kmp)