BZOJ 4560 [JLOI2016]字符串覆盖

这是一道如果没想清楚就不要乱打的题目,
否则就像我一样~~

题目描述

字符串A有N个子串B1,B2,…,Bn。如果将这n个子串分别放在恰好一个它在A中出现的位置上(子串之间可以重叠)这样A中的若干字符就被这N个子串覆盖了。问A中能被覆盖字符个数的最小值和最大值。
输入输出格式
输入格式:

第一行包含一个正整数T,表示数据组数。保证T<=10。

接下来依次描述T组数据,每组数据中:

第一行包含一个由小写字母组成的字符串,表示母串A。

第二行包含一个整数N,表示子串的个数。

接下来N行,每行包含一个由小写字母组成的字符串,描述子串。数据保证所有子串均在母串中出现。

输出格式:

输出为T行,对应每组数据的答案。每行包含两个整数Minans和Maxans,分别表示对应数据中能被覆盖字符数量的最小值和最大值。

输入输出样例
输入样例#1: 复制

2
hello
4
he
l
l
o
abacaba
4
ab
ba
a
c

输出样例#1: 复制

4 5
4 6

说明

字符串长度A<=10000,N<=4,子串长充<=1000

思考了接近一个小时,我想到了一种很难打的dp
设dp[i][S][j]表示到了原串的第i个位置,已经选了集合的状态为S,右端点最远覆盖到j。
这样就可以分情况讨论转移
其中now代表以i位置为左端点的串是哪个,r代表当前串的右端点
1.r’ < i时,dp[i][S | (1 << now)][r] = min{dp[j][s][r’] + (r - i + 1)}(1 <= j <= i-1)
2.i<= r’ < r时,dp[i][S | (1 << now)][r] = min{dp[j][s][r’] + r-r’}(1 <= j <= i-1)
3.r’ > i时,dp[i][S | (1 << now)][r’] = min{dp[j][s][r’]}(1 <= j <= i-1)

于是我们可以发现可以以每一个状态建立一棵线段树方便转移
至于有哪些串以当前位置为左端点可以kmp预处理,再用vector保存
但是我们发现这样会有一个严重的问题,就是当出现第3种情况时
也就是r’ > i时,转移到的状态最右端点变成了r’,而如果是这样,就意味着
我们需要枚举r’,这样复杂度突然就多了一个n,是无法承受的

事实上,这种情况,也就是区间互相包含的情况,需要特殊处理
我们在开始还要建立一棵线段树,预处理出每一个区间里所有包含的区间的二进制表示,也就是上面的now变成了多个串的二进制表示,这样最小值就可以直接转移了
但是最大值又会有问题,因为如果(S&now)不为零,那么就无法转移,结果会变大。所以只有在S&now不为零的时候转移,但又要考虑所有状态,所以还要枚举now的子集转移,复杂度变成了3^n *n *log2(n),常数巨大,还要卡一下常数。

如果你已经懂了思想,就自己写吧。
但如果有兴趣,也不妨看看我8.2k的代码

#include

#define ll long long
#define mm(a, b) memset(a, b, sizeof(a))
#define inf 999999999

#define For(i, a, b) for(int i = (a);i <= (b); ++i)
#define rep(i, a, b) for(int i = (a);i >= (b); --i)

#define gnn cerr << "GNN睡着了" << endl;

using namespace std;

int read(){
    int sum = 0, fg = 1;
    char c = getchar();

    while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
    while(c >='0' && c <='9')sum = (sum << 1) + (sum << 3) + c-'0', c = getchar();

    return sum * fg;
}

const int maxn = 50010;

void file(){
#ifndef ONLINE_JUDGE
    freopen("Thunder.in", "r", stdin);
    freopen("Thunder.out", "w", stdout);
#endif
}

char a[maxn], ss[10][maxn];

int n, Next[maxn], len[maxn];

vector<int> pos[maxn], ans[maxn];

void Get(){
    scanf("%s", a+1);
    n = read();
    For(i, 1, n){
        scanf("%s", ss[i] + 1);
        len[i] = strlen(ss[i] + 1);
    }

    len[0] = strlen(a+1);
}

void Next_Done(int h){
    int k = 0;
    Next[1] = 0;
    For(i, 2, len[h]){
        while(k && ss[h][k+1] != ss[h][i])k = Next[k];

        if(ss[h][k+1] == ss[h][i]) ++k;
        else k = 0;

        Next[i] = k;
    }
}

void Solve(int h){
    int j = 0;
    For(i, 1, len[0]){
        while(j && ss[h][j+1] != a[i])j = Next[j];
        if(ss[h][j+1] == a[i]){
            ++ j;
            if(j == len[h]){
                pos[i - len[h] + 1].push_back(h);
                j = Next[j];
            }
        }
        else j = 0;
    }
}

void pre_work_kmp(){
    For(i, 1, n){
        For(j, 0, len[0] + 1) Next[j] = 0;
        Next_Done(i);
        Solve(i);
    }
}

int fugai[maxn];

int NOWANS;

void insert_fugai(int h,int l,int r,int loc,int now){
    if(l == r){
        fugai[h] |= (1 << now-1);
        return;
    }

    int mid = l+r >> 1;
    if(loc <= mid) insert_fugai(h << 1, l, mid, loc, now);
    else insert_fugai(h << 1 | 1, mid+1, r, loc, now);

    fugai[h] = (fugai[h << 1] | fugai[h << 1 | 1]);
}

int query_fugai(int h,int l,int r,int s,int e){
    if(l == s && r == e) return fugai[h];

    int mid = l+r >> 1;
    if(e <= mid) return query_fugai(h << 1, l, mid, s, e);
    else if(s > mid) return query_fugai(h << 1 | 1, mid+1, r, s, e);
    else return (query_fugai(h << 1, l, mid, s, mid) | query_fugai(h << 1 | 1, mid+1, r, mid+1, e));
}

void print(int h){
    For(i, 1, n) if(h & (1 << i-1)) printf("1"); else printf("0"); puts("");
}

void pre_work_fugai(){
    rep(i, len[0], 1){
        for(int j = 0;j < pos[i].size(); ++j){
            int v = pos[i][j];

            int R = i + len[v] - 1;

            insert_fugai(1, 1, len[0], R, v);
        }

        for(int j = 0;j < pos[i].size(); ++j){
            int v = pos[i][j];

            int R = i + len[v] - 1;
            int Now = (query_fugai(1, 1, len[0], i, R) | (1 << v-1));

            ans[i].push_back(Now);
        }
    }
}

int tree_one[(1 << 4) + 10][maxn << 2], tree_two[(1 << 4) + 10][maxn << 2], dp[(1 << 4) + 10][maxn];

void pre_work(){
    For(i, 0, len[0]) ans[i].clear(), pos[i].clear();
    mm(fugai, 0);
    pre_work_kmp();
    pre_work_fugai();
}

void insert_one(int h,int l,int r,int S,int loc,int val){
    if(l == r){
        tree_one[S][h] = min(tree_one[S][h], val);
        return;
    }

    int mid = l+r >> 1;
    if(loc <= mid) insert_one(h << 1, l, mid, S, loc, val);
    else insert_one(h << 1 | 1, mid+1, r, S, loc, val);

    tree_one[S][h] = min(tree_one[S][h << 1], tree_one[S][h << 1 | 1]);
}

void query_one(int h,int l,int r,int s,int e,int S){
    if(tree_one[S][h] >= NOWANS) return;
    if(l == s && r == e){
        NOWANS = tree_one[S][h];
        return ;
    }

    int mid = l+r >> 1;
    if(e <= mid) query_one(h << 1, l, mid, s, e, S);
    else if(s > mid) query_one(h << 1 | 1, mid+1, r, s, e, S);
    else return query_one(h << 1, l, mid, s, mid, S), query_one(h << 1 | 1, mid+1, r, mid+1, e, S);
}

void insert_two(int h,int l,int r,int S,int loc,int val){
    if(l == r){
        tree_two[S][h] = min(tree_two[S][h], val - l);
        return;
    }

    int mid = l+r >> 1;
    if(loc <= mid) insert_two(h << 1, l, mid, S, loc, val);
    else insert_two(h << 1 | 1, mid+1, r, S, loc, val);

    tree_two[S][h] = min(tree_two[S][h << 1], tree_two[S][h << 1 | 1]);
}

void query_two(int h,int l,int r,int s,int e,int S){
    if(tree_two[S][h] >= NOWANS) return;
    if(l == s && r == e){
        NOWANS = tree_two[S][h];
        return ;
    }

    int mid = l+r >> 1;
    if(e <= mid) query_two(h << 1, l, mid, s, e, S);
    else if(s > mid) query_two(h << 1 | 1, mid+1, r, s, e, S);
    else return query_two(h << 1, l, mid, s, mid, S), query_two(h << 1 | 1, mid+1, r, mid+1, e, S);
}

int ls[(1 << 4) + 10][maxn << 2];

void solve_min(){
    int tmp = (1 << n) - 1;

    for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0]; ++j) dp[i][j] = inf;
    for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0] * 4; ++j) tree_one[i][j] = tree_two[i][j] = inf;

    dp[0][0] = 0;
    insert_one(1, 0, len[0], 0, 0, 0);
    insert_two(1, 0, len[0], 0, 0, 0);

    int Min = inf;

    For(i, 1, len[0]){
        For(j, 0, tmp - 1){
            for(int k = 0;k < pos[i].size(); ++k){
                int v = ans[i][k];

                int R = i + len[pos[i][k]] - 1;
                ls[j][k] = dp[j | v][R];
                if((j | v) == j) continue;

                int nowval = inf;
                NOWANS = inf;
                query_one(1, 0, len[0], 0, i-1, j);
                nowval = min(nowval, NOWANS + len[pos[i][k]]);

                NOWANS = inf;
                query_two(1, 0, len[0], i, R, j);
                nowval = min(nowval, NOWANS + R);

                dp[j | v][R] = min(dp[j | v][R], nowval);

                if(dp[j | v][R] < ls[j][k] && dp[j | v][R] < inf / 2){
                    insert_one(1, 0, len[0], (j | v), R, dp[j | v][R]);
                    insert_two(1, 0, len[0], (j | v), R, dp[j | v][R]);
                }
            }
        }

        Min = min(Min, dp[tmp][i]);
    }

    printf("%d ", Min);
}

void _insert_one(int h,int l,int r,int S,int loc,int val){
    if(l == r){
        tree_one[S][h] = max(tree_one[S][h], val);
        return;
    }

    int mid = l+r >> 1;
    if(loc <= mid) _insert_one(h << 1, l, mid, S, loc, val);
    else _insert_one(h << 1 | 1, mid+1, r, S, loc, val);

    tree_one[S][h] = max(tree_one[S][h << 1], tree_one[S][h << 1 | 1]);
}

void _query_one(int h,int l,int r,int s,int e,int S){
    if(tree_one[S][h] < NOWANS) return;
    if(l == s && r == e){
        NOWANS = tree_one[S][h];
        return;
    }

    int mid = l+r >> 1;
    if(e <= mid) _query_one(h << 1, l, mid, s, e, S);
    else if(s > mid) _query_one(h << 1 | 1, mid+1, r, s, e, S);
    else return _query_one(h << 1, l, mid, s, mid, S), _query_one(h << 1 | 1, mid+1, r, mid+1, e, S);
}

void _insert_two(int h,int l,int r,int S,int loc,int val){
    if(l == r){
        tree_two[S][h] = max(tree_two[S][h], val - l);
        return;
    }

    int mid = l+r >> 1;
    if(loc <= mid) _insert_two(h << 1, l, mid, S, loc, val);
    else _insert_two(h << 1 | 1, mid+1, r, S, loc, val);

    tree_two[S][h] = max(tree_two[S][h << 1], tree_two[S][h << 1 | 1]);
}

void _query_two(int h,int l,int r,int s,int e,int S){
    if(tree_two[S][h] < NOWANS) return;
    if(l == s && r == e){
        NOWANS = tree_two[S][h];
        return;
    }

    int mid = l+r >> 1;
    if(e <= mid) _query_two(h << 1, l, mid, s, e, S);
    else if(s > mid)  _query_two(h << 1 | 1, mid+1, r, s, e, S);
    else return _query_two(h << 1, l, mid, s, mid, S), _query_two(h << 1 | 1, mid+1, r, mid+1, e, S);
}

void solve_max(){
    int tmp = (1 << n) - 1;

    for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0]; ++j) dp[i][j] = -inf;
    for(int i = 0;i <= tmp; ++i) for(int j = 0;j <= len[0] * 4; ++j) tree_one[i][j] = tree_two[i][j] = -inf;

    dp[0][0] = 0;
    _insert_one(1, 0, len[0], 0, 0, 0);
    _insert_two(1, 0, len[0], 0, 0, 0);

    int Max = -inf;

    For(i, 1, len[0]){
        For(j, 0, tmp){
            for(int k = 0;k < pos[i].size(); ++k){
                int v = ans[i][k];

                int R = i + len[pos[i][k]] - 1;
                if(j & (1 << pos[i][k]-1)) continue;

                int nowval = 0;
                NOWANS = -inf;
                _query_one(1, 0, len[0], 0, i-1, j);
                nowval = max(nowval, NOWANS + len[pos[i][k]]);

                NOWANS = -inf;
                _query_two(1, 0, len[0], i, R, j);
                nowval = max(nowval, NOWANS + R);

                int now = (v ^ (1 << pos[i][k]-1));
                int T = (j | (1 << pos[i][k]-1));

                int Just = dp[T][R];
                dp[T][R] = max(dp[T][R], nowval);

                if(dp[T][R] > Just){
                    _insert_one(1, 0, len[0], T, R, dp[T][R]);
                    _insert_two(1, 0, len[0], T, R, dp[T][R]);
                }

                for(int o = now;o ;o = ((o-1)&now)){
                    Just = dp[T | o][R];
                    dp[T | o][R] = max(dp[T | o][R], nowval);
                    if(dp[T | o][R] > Just){
                        _insert_one(1, 0, len[0], (T | o), R, dp[T | o][R]);
                        _insert_two(1, 0, len[0], (T | o), R, dp[T | o][R]);
                    }
                }
            }
        }

        Max = max(Max, dp[tmp][i]);
    }

    printf("%d\n", Max);
}

int main(){

    file();
    int _ = read();
    while(_ --){
        Get();
        pre_work();
        solve_min();
        solve_max();
    }

    return 0;
}

你可能感兴趣的:(dp,数据结构)