【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;
}

原文地址:https://www.cnblogs.com/rentu/p/11410359.html

时间: 2024-11-13 09:58:07

【SDOI2008】Sandy的卡片的相关文章

BZOJ 4698: Sdoi2008 Sandy的卡片

4698: Sdoi2008 Sandy的卡片 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 225  Solved: 95[Submit][Status][Discuss] Description Sandy和Sue的热衷于收集干脆面中的卡片.然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积 攒卡片兑换超炫的人物模型.每一张卡片都由一些数字进行标记,第i张卡片的序列长度为Mi,要想兑换人物模型 ,首先必须要集够N张卡片,对

[Sdoi2008]Sandy的卡片

[Sdoi2008]Sandy的卡片 题目 Sandy和Sue的热衷于收集干脆面中的卡片.然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积攒卡片兑换超炫的人物模型.每一张卡片都由一些数字进行标记,第i张卡片的序列长度为Mi,要想兑换人物模型,首先必须要集够N张卡片,对于这N张卡片,如果他们都有一个相同的子串长度为k,则可以兑换一个等级为k的人物模型.相同的定义为:两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串.Sandy的卡片数远远小于要求的N,于是Sue决定

bzoj4698 / P2463 [SDOI2008]Sandy的卡片

P2463 [SDOI2008]Sandy的卡片 直接二分长度暴力匹配....... 跑的还挺快 (正解是后缀数组的样子) 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 void read(int &x){ 6 char c=getchar();x=0; 7 while(c<'0'||c>'9')c=getchar(); 8 w

SDOI2008 Sandy的卡片( 后缀数组 )

求出后缀数组, 然后二分答案, 对height数组分组检验答案. 时间复杂度O(|S| log|S|) -------------------------------------------------------------------------------- #include<cstdio> #include<cctype> #include<cstring> #include<algorithm> using namespace std; const

luogu 2463 [SDOI2008]Sandy的卡片 kmp || 后缀数组 n个串的最长公共子串

题目链接 Description 给出\(n\)个序列.找出这\(n\)个序列的最长相同子串. 在这里,相同定义为:两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串. 思路 参考:hzwer. 法一:kmp 在第一个串中枚举答案串的开头位置,与其余\(n-1\)个串做\(kmp\). 法二:后缀数组 将\(n\)个串拼接起来.二分答案\(len\),将\(height\)分组,\(check\)是否有一组个数\(\geq len\)且落在\(n\)个不同的串中. 注意:\(n\)个串

BZOJ4698 [SDOI2008] Sandy的卡片 - 后缀数组,二分

题意:求在N个串中都出现的最长子串 的长度 很容易想到二分转化为判定性问题.考虑长度M,我们按照长度M进行分组,每个组内进行答案验证,即检查组内是否有N个串的后缀都出现. 时间复杂度O(LlogM) 其实是个经典题. 二分时候记得初始化! 1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct SA { 5 int str[2100005]; 6 int x[2100005],y[2100005],u[2100005],v[21

[Luogu2463][SDOI2008]Sandy的卡片

BZOJ权限题qwq Luogu sol "两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串" 其实就是差分一波以后完全相同 所以对输入的数据进行差分,同时记一下每一个位置是属于哪个串的. 记得在串与串中间加入一个没有出现的字符. 求出SA后二分答案\(mid\),问题变成:\(Height\)数组里是否存在一个大于等于\(mid\)的连续段使每个串都在里面出现过. 开桶记录.记一个\(id\)避免每次对桶的清空. code #include<cstdio> #

P2463 [SDOI2008]Sandy的卡片

题目链接 \(Click\) \(Here\) 真的好麻烦啊..事实证明,理解是理解,一定要认认真真把板子打牢,不然调锅的时候真的会很痛苦..(最好是八分钟能无脑把\(SA\)码对的程度\(QAQ\)) 这个题最开始我想的是\(RMQ\)遍历每一个子区间,但是意识到复杂度是\(O(N^2)\)然后就\(GG\)了.怎么说呢,后缀数组和二分似乎是很常见的组合(和莫队也是?),这个题只需要在\(height\)数组里二分\(lcp\)长度即可,\(check\)函数里面处理一下,要让区间内所有原串都

[SDOI2008][luogu2463] Sandy的卡片 [kmp]

题面 传送门 思路 这道题里面有三个主要问题: 1.由"数值相等"变成了"加上一个整数以后数值相等"(减去等价于加负数) 2.由"最多匹配多少位(从第一位开始)"变成了从"任意一位开始匹配" 3.由"单文本串"变成了"多文本串",而且是文本串内部自己匹配 我们按照难度顺序(3-1-2)来依次解决他们 问题三:多文本串 这个问题是最简单的,因为每个串长度不超过100,最多1000个串 所以