【SDOI2008】Sandy的卡片(后缀数组)

题目描述

Sandy和Sue的热衷于收集干脆面中的卡片。

然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积攒卡片兑换超炫的人物模型。

每一张卡片都由一些数字进行标记,第i张卡片的序列长度为Mi,要想兑换人物模型,首先必须要集够N张卡片,对于这N张卡片,如果他们都有一个相同的子串长度为k,则可以兑换一个等级为k的人物模型。相同的定义为:两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串。

Sandy的卡片数远远小于要求的N,于是Sue决定在Sandy的生日将自己的卡片送给Sandy,在Sue的帮助下,Sandy终于集够了N张卡片,但是,Sandy并不清楚他可以兑换到哪个等级的人物模型,现在,请你帮助Sandy和Sue,看看他们最高能够得到哪个等级的人物模型。

输入格式

第一行为一个数N,表示可以兑换人物模型最少需要的卡片数,即Sandy现在有的卡片数

第i+1行到第i+N行每行第一个数为第i张卡片序列的长度Mi,之后j+1到j+1+Mi个数,用空格分隔,分别表示序列中的第j个数

输出格式

一个数k,表示可以获得的最高等级。

输入输出样例

输入 #1
  2
2 1 2
3 4 5 9
输出 #1
2

说明/提示

数据范围:

30%的数据保证n<=50

100%的数据保证n<=1000,M<=1000,2<=Mi<=101

update:题面上数据范围mi和m的范围其实是一个东西... 真实数据范围:40<=n<=1000,2<=mi<=101,字符串中的每个数字的大小范围为[0,1864]

 

 

【题目大意】

  给定 n 个序列,求 n 个序列中相同的子串的最大长度,相同的定义是给每个子串里面的每个数加上一个数等于另外一个序列

【思路描述】

  首先是想到将问题转化为后缀数组进行一个字符串的操作

  我们可以想到,当前后两数之差和另外一个序列中的另外相邻两数之差相等时

  给前者两个数加一个相同的数就能够得到另外一个序列

  所以问题就能够转化为 连续相邻两数之差相同的最长子串长度

  子串,相同,数字一定属于一定范围而且比较小,好,后缀数组能写

  把所有序列拼成一个序列,做成一个字符串

  每个序列中间用一个不同大数隔开

  然后怎么查找这个长度呢

  二分这个长度

  然后考虑 check 函数怎么写

  比较 height [ i ] 当 i 的height [ i ] 大于长度的时候,说明这个序列符合要求

  因为是按照字典序在检索,所以能够不错漏,

  其实有点像是在找 height 的最小值,同时保证每个最小值在不同的序列中

  当找到 n 个的时候说明每个序列都有大于等于这个长度的相同子串

  没有找到的时候说明没有,

  然后二分

  最后输出答案,因为我们判断的是差,而第一个字符没有被判定

  所以答案 + 1

 


【代码】

 

注意:
1 . 因为是按照数字的大小排序,所以注意处理差的正负,把所有的负差需要变成正的
     同时加一个数不破坏这个序列的属性,直接加最小值即可
2 . 因为有可能前后两个数字相同,于是差是0,为了和开头的0避开,给 0+1 
 
 
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAX = 1010;
const int MAXM = 6010;
const int MAXN = MAX * (MAXM + 1);
const int DOT = 2000;
int DOWN = 2000;

int n = 0,num,m=-1;
int  a[MAX][MAXM], len[MAXM],b[MAXN];
int  SA[MAXN], height[MAXN];
int rak[MAXN],id[MAXN];
int  c[MAXN], x[MAXN], y[MAXN];
bool vis[MAXN];
void get_SA(int *s)
{
    //初始化
    for (int i = 1; i <= n; i++)
        ++c[x[i] = s[i]];
    //计算每一个字符的数量,同样的放在一起
    for (int i = 2; i <= m; i++)
        c[i] += c[i - 1];
    //求前缀和
    for (int i = n; i >= 1; i--)
        SA[c[x[i]]--] = i;
    //从后往前,这样就能求出最开始顺序

    for (int k = 1; k <= n; k <<= 1)
    {
        int num = 0;
        for (int i = n - k + 1; i <= n; i++)    y[++num] = i;
        //把最后几个字符放进去
        for (int i = 1; i <= n; i++)
            //按照顺序遍历
            if (SA[i] > k)
                //如果长度大于k
                y[++num] = SA[i] - k;
        //把第一个放进去
        for (int i = 1; i <= m; i++)
            c[i] = 0;
        for (int i = 1; i <= n; i++)
            c[x[i]]++;
        //x[i]是第一关键字
        for (int i = 2; i <= m; i++)
            c[i] += c[i - 1];
        //求前缀和
        for (int i = n; i >= 1; i--)
            SA[c[x[y[i]]]--] = y[i], y[i] = 0;
        swap(x, y);
        x[SA[1]] = 1; num = 1;
        for (int i = 2; i <= n; i++)
            x[SA[i]] = (y[SA[i]] == y[SA[i - 1]] && y[SA[i] + k] == y[SA[i - 1] + k]) ? num : ++num;
        if (num == n) break;
        m = num;
    }
    for (int i = 1; i <= n; i++)
        rak[SA[i]] = i;
    return;
}
void get_height(int *s)
{
    int j, k = 0;
    for (int i = 1; i <= n; i++) {
        if (k) k--;
        j = SA[rak[i] - 1];
        while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) k++;
        height[rak[i]] = k;
    }
}
int sta[MAXN];
int top = 0;
bool check(int x)
{
    while (top) vis[sta[top--]] = 0;
    for (int i = 1; i <= n; i++)
    {
        if (height[i] < x)
        {
            while(top) vis[sta[top--]] = 0;
        }
        if (!vis[id[SA[i]]])
        {
            vis[id[SA[i]]] = 1;
            sta[++top] = id[SA[i]];
            if (top == num)
                return 1;
        }
    }
    return 0;
}
int main()
{
    int minn=(1<<32)-1;
    int r = -1;
    scanf("%d", &num);

    for (int i = 1; i <= num; i++)
    {
        scanf("%d", &len[i]);
        r = max(r, len[i]);
        for (int j = 1; j <= len[i]; j++)
        {
            scanf("%d", &a[i][j]);
        }
    }
    n = 0;
    for (int i = 1; i <= num; i++)
    {
        
        for (int j = 2; j <= len[i]; j++)
        {
            b[++n] = a[i][j] - a[i][j - 1]+1;
            id[n]  = i;
            minn = min(b[n], minn);
        }
        b[++n] = ++DOWN;
    }
    for (int i = 1; i <= n; i++)
    {
        if (minn <= 0)
            b[i] -= minn + 1;
        else
            b[i]++;
        m = max(b[i], m);
    }
    get_SA(b);
    //printf("**\n");
    get_height(b);
    //printf("**\n");
    int ans=0;
    int l = 1;
    
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        if (check(mid))
        {
            ans = mid;
            l = mid + 1;
        }
        else
        {
            r = mid - 1;
        }
    }
    //printf("**\n");
    printf("%d", ans+1);
    return 0;
}
View Code

 

posted @ 2019-08-26 08:38  rentu  阅读(158)  评论(0编辑  收藏  举报