BZOJ 2806 (ctsc 2012) Cheat 后缀自动机预处理 + DP

题目大意:

就是现在有m个01串代表搜集的文章, 现在要检查n篇作文, 对于每一篇作文找到最大的L值, 一篇文章的L值可以将这篇文章分割成多个部分, 其中长度>=L的且在m个01串中出现过的串称之为熟悉的串, 熟悉的串的长度加起来要达到文章总长度的90%就称这篇文章熟悉, 求最大的L使得这篇文章熟悉


大致思路:

首先将m篇文章连起来中间用另外的字符隔开, 然后将要检查的作文在这个自动机上惊醒遍历, 得到每个位置的字符向前匹配能得到的最大长度

不放设每个位置向前恩那个匹配到的最大长度为match[i]

对于前i个字符在长度L要求下能匹配到的最大长度记为dp[i], 那么不难发现首先dp[i]至少等于dp[i - 1], 然后可能取到dp[i] = max(dp[j] + i - j, i - match[i] <= j <= i - L)

那么我们对于dp[i] = max(dp[j] + i - j, i - match[i] <= j <= i - L)可以转换为dp[i] - i = max(dp[j] - j, i - match[i] <= j <= i - L), 因为不难发现dp[i]的意义可得dp[i] <= dp[i + 1]<= dp[i + 2]...

即dp满足不减性, 那么dp[i] - i在最好的情况下(即dp[i] = i全部匹配的情况下dp[i] - i = 0), dp[i] - i满足不增性, 也就是说dp[i] - i一定是单调的

那么当dp[i] - i要取到max(dp[j] - j, i - match[i] <= j <= i - L)中的最大值时, 由于dp[j] - j满足不增性, 如果有dp[i - L] - (i - L)最大时, 最优解一定是dp[i] = dp[j] - j + i ( j = i - L)

由于dp[i] - i <= dp[i + 1] - (i + 1)当dpi - L] - (i - L)不是最大时, 最优解取的是[i - match[i], i - L]中的第一个, 由于i- match[i]也是单调的, 所以只需要用一个队列维护一下就行了, 复杂度降为O(n), 至于如何确定L, 二分一下就行了


代码如下:

Result  :  Accepted     Memory  :  98112 KB     Time  :  988 ms

/**************************************************************
    Problem: 2806
    User: Gatevin
    Language: C++
    Result: Accepted
    Time:988 ms
    Memory:98112 kb
****************************************************************/
 
/*
 * Author: Gatevin
 * Created Time:  2015/4/27 17:43:08
 * File Name: Rin_Tohsaka.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define foreach(e, x) for(__typeof(x.begin()) e = x.begin(); e != x.end(); ++e)
#define SHOW_MEMORY(x) cout<<sizeof(x)/(1024*1024.)<<"MB"<<endl
 
#define maxn 4444444
#define maxs 1111111
 
struct Suffix_Automation
{
    struct State
    {
        State *par;
        State *go[3];
        int val;
        void init(int _val = 0)
        {
            par = 0, val = _val;
            memset(go, 0, sizeof(go));
        }
    };
    State *last, *root, *cur;
    State nodePool[maxn];
    State* newState(int val = 0)
    {
        cur->init(val);
        return cur++;
    }
    void init()
    {
        cur = nodePool;
        root = newState();
        last = root;
    }
    void extend(int w)
    {
        State *p = last;
        State *np = newState(p->val + 1);
        while(p && p->go[w] == 0)
        {
            p->go[w] = np;
            p = p->par;
        }
        if(p == 0)
        {
            np->par = root;
        }
        else
        {
            State *q = p->go[w];
            if(p->val + 1 == q->val)
            {
                np->par = q;
            }
            else
            {
                State *nq = newState(p->val + 1);
                memcpy(nq->go, q->go, sizeof(q->go));
                nq->par = q->par;
                q->par = nq;
                np->par = nq;
                while(p && p->go[w] == q)
                {
                    p->go[w] = nq;
                    p = p->par;
                }
            }
        }
        last = np;
    }
    int match[maxs];//第i个字符向前能匹配到的最大字符数为match[i]
    int dp[maxs];//dp[i]表示前i个字符中能分割出的最多的匹配字符数
    bool check(int L0, int len)
    {
        queue <int> Q;
        dp[0] = 0;
        //Q.push(0);
        for(int i = 1; i <= len; i++)
        {
            dp[i] = dp[i - 1];
            if(i - L0 >= 0)
            {
                if(!Q.empty() && dp[i - L0] - (i - L0) > dp[Q.front()] - Q.front())
                    while(!Q.empty()) Q.pop();//最优解如果有一定是i - L0
                Q.push(i - L0);
            }
            while(!Q.empty() && Q.front() < i - match[i])//j >= i - match[i]
                Q.pop();
            if(!Q.empty())
                dp[i] = max(dp[i], dp[Q.front()] + i - Q.front());
        }
        if(dp[len]*10 >= 9*len) return true;
        else return false;
    }
    void solve(char *s)
    {
        int len = strlen(s);
        State *now = root;
        int cnt = 0;
        for(int i = 0; i < len; i++)//预处理出match[i]数组
        {
            int w = s[i] - '0';
            if(now->go[w])
            {
                now = now->go[w];
                cnt++;
                match[i + 1] = cnt;
            }
            else
            {
                while(now && now->go[w] == 0)
                {
                    now = now->par;
                    if(now)
                        cnt = now->val;
                }
                if(now == 0)
                    now = root, cnt = 0;
                else
                {
                    now = now->go[w];
                    cnt++;
                    match[i + 1] = cnt;
                }
            }
        }
        int L = 0, R = len, mid;
        int ans = 0;
        while(L <= R)
        {
            mid = (L + R) >> 1;
            if(check(mid, len))
            {
                L = mid + 1;
                ans = mid;
            }
            else R = mid - 1;
        }
        printf("%d\n", ans);
    }
};
 
Suffix_Automation sam;
char s[maxs];
int n, m;
 
int main()
{
    scanf("%d %d", &n, &m);
    sam.init();
    while(m--)
    {
        scanf("%s", s);
        int len = strlen(s);
        for(int i = 0; i < len; i++)
            sam.extend(s[i] - '0');
        sam.extend(2);
    }
    while(n--)
        scanf("%s", s), sam.solve(s);
    return 0;
}



另外附上一个错误的想法, 错在将dp[i]认为是单调的了, 实际上单调的是dp[i] - i而并不是dp[i]

Result  :  Wrong Answer     Memory  :  --     Time :  --

/*
 * Author: Gatevin
 * Created Time:  2015/4/27 17:43:08
 * File Name: Rin_Tohsaka.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define foreach(e, x) for(__typeof(x.begin()) e = x.begin(); e != x.end(); ++e)
#define SHOW_MEMORY(x) cout<<sizeof(x)/(1024*1024.)<<"MB"<<endl

#define maxn 4444444
#define maxs 1111111

struct Suffix_Automation
{
    struct State
    {
        State *par;
        State *go[3];
        int val;
        void init(int _val = 0)
        {
            par = 0, val = _val;
            memset(go, 0, sizeof(go));
        }
    };
    State *last, *root, *cur;
    State nodePool[maxn];
    State* newState(int val = 0)
    {
        cur->init(val);
        return cur++;
    }
    void init()
    {
        cur = nodePool;
        root = newState();
        last = root;
    }
    void extend(int w)
    {
        State *p = last;
        State *np = newState(p->val + 1);
        while(p && p->go[w] == 0)
        {
            p->go[w] = np;
            p = p->par;
        }
        if(p == 0)
        {
            np->par = root;
        }
        else
        {
            State *q = p->go[w];
            if(p->val + 1 == q->val)
            {
                np->par = q;
            }
            else
            {
                State *nq = newState(p->val + 1);
                memcpy(nq->go, q->go, sizeof(q->go));
                nq->par = q->par;
                q->par = nq;
                np->par = nq;
                while(p && p->go[w] == q)
                {
                    p->go[w] = nq;
                    p = p->par;
                }
            }
        }
        last = np;
    }
    int match[maxs];//第i个字符向前能匹配到的最大字符数为match[i]
    int dp[maxs];//dp[i]表示前i个字符中能分割出的最多的匹配字符数
    bool check(int L0, int len)
    {
        queue <int> Q;
        dp[0] = 0;
        Q.push(0);
        for(int i = 1; i <= len; i++)
        {
            dp[i] = dp[i - 1];
            while(!Q.empty() && Q.front() < i - match[i])//筛除边界条件
                Q.pop();
            if(!Q.empty() && Q.front() <= i - L0)
                dp[i] = max(dp[i], dp[Q.front()] + i - Q.front());
            //这里就是认为dp[i]是不减的, 弄成了贪心, 其实dp[i]的最优解不一定来自最前面一个位置
            Q.push(i);
        }
        if(dp[len]*10 >= 9*len) return true;
        else return false;
    }
    void solve(char *s)
    {
        int len = strlen(s);
        State *now = root;
        int cnt = 0;
        for(int i = 0; i < len; i++)//预处理出match[i]数组
        {
            int w = s[i] - '0';
            if(now->go[w])
            {
                now = now->go[w];
                cnt++;
                match[i + 1] = cnt;
            }
            else
            {
                while(now && now->go[w] == 0)
                {
                    now = now->par;
                    if(now)
                        cnt = now->val;
                }
                if(now == 0)
                    now = root, cnt = 0;
                else
                {
                    now = now->go[w];
                    cnt++;
                    match[i + 1] = cnt;
                }
            }
        }
        int L = 1, R = len, mid;
        int ans = 0;
        while(L <= R)
        {
            mid = (L + R) >> 1;
            if(check(mid, len))
            {
                L = mid + 1;
                ans = mid;
            }
            else R = mid - 1;
        }
        printf("%d\n", ans);
    }
};

Suffix_Automation sam;
char s[maxs];
int n, m;

int main()
{
    scanf("%d %d", &n, &m);
    sam.init();
    while(m--)
    {
        scanf("%s", s);
        int len = strlen(s);
        for(int i = 0; i < len; i++)
            sam.extend(s[i] - '0');
        sam.extend(2);
    }
    while(n--)
        scanf("%s", s), sam.solve(s);
    return 0;
}


你可能感兴趣的:(2012,后缀自动机,Cheat,bzoj,cstc,2806)