[sdoi2008] Sandy的卡片
时限 0.5s
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,表示可以获得的最高等级。
输入样例:
2
2 1 2
3 4 5 9
输出样例:
2
数据范围:
30%的数据保证n<=50
100%的数据保证n<=1000
题解
出题人:
注意到是公共最长子串,所以以第一个串为基本串,枚举答案串的开头和结尾,然后再在每个剩余的串中寻找,如果每个串中都包含,显然是个可行解.
这样做的时间复杂度过高,只能过掉30%的数据.
将这个算法优化,仅仅枚举答案串的开头,用kmp去寻找每个串最多能匹配基本串的长度,取最小值,即可行解.这样算法复杂度为O(NM^2),可以得到满分.
使用后缀数组,可以将算法复杂度进一步优化,将所有串连接,二分答案按照m对后缀分组,判断是否有一组有n个后缀其首字母属于不同的串
sa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include<set> #include<map> #include<ctime> #include<queue> #include<cmath> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define inf 1000000000 #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,tot,top; int p,q=1,k; int mx,mn=inf,L=1,R=inf; int st[1005]; int l[1005],a[1005][1005],b[1000005],id[1000005]; int v[1000005],h[1000005]; int sa[2][1000005],rk[2][1000005]; bool vis[1005]; void mul(int *sa,int *rk,int *SA,int *RK) { for(int i=1;i<=tot;i++)v[rk[sa[i]]]=i; for(int i=tot;i;i--) if(sa[i]>k)SA[v[rk[sa[i]-k]]--]=sa[i]-k; for(int i=tot-k+1;i<=tot;i++)SA[v[rk[i]]--]=i; for(int i=1;i<=tot;i++) RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]]||rk[SA[i]+k]!=rk[SA[i-1]+k]); } void presa() { for(int i=1;i<=tot;i++)v[b[i]]++; for(int i=1;i<=mx;i++)v[i]+=v[i-1]; for(int i=1;i<=tot;i++)sa[p][v[b[i]]--]=i; for(int i=1;i<=tot;i++) rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(b[sa[p][i]]!=b[sa[p][i-1]]); for(k=1;k<tot;k<<=1,swap(p,q)) mul(sa[p],rk[p],sa[q],rk[q]); for(int i=1,k=0;i<=tot;i++) { int j=sa[p][rk[p][i]-1]; while(b[i+k]==b[j+k])k++; h[rk[p][i]]=k;if(k>0)k--; } } bool check(int mid) { int num=0; for(int i=1;i<=tot;i++) { if(h[i]<mid) { num=0;while(top)vis[st[top--]]=0; } if(!vis[id[sa[p][i]]]) { vis[id[sa[p][i]]]=1;num++;st[++top]=id[sa[p][i]]; } if(num==n)return 1; } return 0; } int main() { n=read(); for(int i=1;i<=n;i++) { l[i]=read(); for(int j=1;j<=l[i];j++) { a[i][j]=read(); if(j!=1)mx=max(mx,a[i][j]-a[i][j-1]); } R=min(R,l[i]-1); } for(int i=1;i<=n;i++) { for(int j=2;j<=l[i];j++) { b[++tot]=a[i][j]-a[i][j-1]; id[tot]=i; } b[++tot]=++mx; } for(int i=1;i<=tot;i++)mn=min(b[i],mn); for(int i=1;i<=tot;i++) { b[i]=b[i]-mn+1; mx=max(mx,b[i]); } presa(); int ans=1; while(L<=R) { int mid=(L+R)>>1; if(check(mid))ans=mid+1,L=mid+1; else R=mid-1; } printf("%d\n",ans); return 0; } |
这道题保证每个串都是递增的吗?差分以后有负的 怎么办
不怎么办。不需要特判负数。
黄学长,您的kmp是wrong answer,从codevs上测得。后缀数组是re,改大数组的范围把所有100005改成1000005才能AC
“两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串。”这是什么意思TAT
就是差分完一样
bzoj上似乎并没有这道题,黄学长是怎么找到这道题的TAT
我从不知道什么地方的模拟赛拿到的