KMP---基因改造
基因改造
(http://oi.nks.edu.cn/zh/Problem/Details/5942)
问题描述
"人类智慧的冰峰,只有萌萌哒的我寂寞地守望。"
--TB
TB正走在改造人类智慧基因的路上。TB发现人类智慧基因一点也不萌萌哒,导致人类普遍智商过低,为了拯救低智商人群,博爱的TB开始改造人类智慧基因。人类智慧DNA由C种人类智慧脱氧核苷酸构成(这是一种十分特殊的DNA)。
TB智慧DNA片段T长度为M,她可以把另一段长度为M的人类智慧DNA片段S改造成T。
改造过程中,TB可以充分发挥智慧,将任意两种人类智慧脱氧核苷酸交换(比如对于片段S=12321,交换1和2变成S=21312,交换1和4变成42324),可以无限次交换。如果S可以通过若干次交换变成T,那么就称S为"萌萌哒人类基因片段"。
TB想知道对于一个长度为N的人类智慧DNA片段S[1 ~ N],有多少个长度为M的连续子片段S[i ~ i+M-1],是"萌萌哒人类基因片段",并且这些"萌萌哒人类基因片段"在哪里。
输入格式
第一行包含两个正整数case和C,分别表示数据组数和人类智慧脱氧核苷酸的种数。
接下来3*case行,每三行表示一组数据:
第一行一个正整数N和M,表示人类智慧DNA片段S和TB智慧DNA片段T的长度。
第二行N个正整数,表示人类智慧DNA片段S。
第三行M个正整数,表示TB智慧DNA片段T。
对于所有数据数据,case=3, n,m,C<=100000
输出格式
对于每组数据:
第一行一个正整数tot,表示"萌萌哒人类基因片段"的个数。
接下来一行tot个用空格隔开的正整数pos,表示"萌萌哒人类基因片段"开头所在的位置。要求从小到大输出每个pos。
样例输入
3 3
6 3
1 2 1 2 3 2
3 1 3
6 3
1 2 1 2 1 2
3 1 3
6 3
1 1 2 1 2 1
3 1 3
样例输出
3
1 2 4
4
1 2 3 4
3
2 3 4
提示
对于第一组数据:
S[ 1 ~ 3]=121,可以先将1和2交换变成212,再将2和3交换变成313。
S[2 ~ 4]=212,可以将2和3交换变成313。
S[4 ~ 6]=232,可以先将2和3交换变成323,再将1和2交换变成313。
分析
置, 由此可以想到 KMP。
题目允许将子串中任意两种数字交换, 这意味着子串中所有的数字都可以改
变, 不变的只有同种数字之间的位置关系。 例如 31321、 13123、 23213 之间
可以互相转换, 在 KMP 时这几个串应该被当作相同的串。 因此用来 KMP 的两
个串必须体现这种位置关系。
对原串进行修改, 把第 i 个位置的数字修改为这个数字上一次出现的位置与当
前位置之差, 若之前没有出现过, 修改为 0。 例如 31321、 13123、 23213 三
个串经修改后都会变为 00203。
但这样修改后也不能够直接 KMP, 因为有 0 的位置并不能完全体现同种数字的
位置关系。 因此对于这些位置要分情况讨论(设当前与 T 匹配的 S 的子串为
S') :
• S'和 T 完全相同, 例如 S'=010, T=010。 这时两个串可以直接被判定为相
同。
• T 的某个位置不为 0, 而 S'的对应位置为 0, 例如 S'=010, T=011。 则 T 的
这个位置一定是一个之前出现过的数字, S'的这个位置一定是一个之前没
有出现过的数字, 两个串肯定不同。
• T 的某个位置为 0, 而 S'的对应位置不为 0, 这里给两个例子:
• S'=012, T=002。 S'中第二个位置的数字应该与第一个位置相同, 但 T 的前
两个位置的数字都是第一次出现, 肯定不相同, 因此这两个串不相同。
• S'=022, T=002。 S'中第二个位置的数字应该与在它前面距离为 2 的位置相
同, 而这个位置不在子串 S'内, 所以这个数字仍被当作第一次出现, 因此
这两个串相同。
因此 KMP 匹配时需要重新定义比较法则, 具体实现见代码。
题目中的 C 没有用处, 各位神犇不要多虑。
代码
#include<stdio.h> #include<bits/stdc++.h> using namespace std; const int maxn=100000+5; int a[maxn],b[maxn],mark[maxn],ans,pos[maxn],Next[maxn]; bool kmp(int l,int r) { int j=0; for (int i=l; i<=r; i++) { int x=a[i]; if (i-a[i]<l) x=0; while (j>0&&b[j+1]!=x) j=Next[j]; if (x==b[j+1]) j++; if (j==r-l+1) return true; } return false; } int main() { ios::sync_with_stdio(false); cout.tie(NULL); int n,m; cin>>n>>m; for (int i=1; i<=n; i++) { int y,z; cin>>y>>z; for (int j=1; j<=y; j++) //人类智慧DNA片段S { int x; cin>>x; if (mark[x])//x出现过 a[j]=j-mark[x];//计算大小 else a[j]=0;//未出现过,赋值为0 mark[x]=j;//标记已出现,储存位置 } memset(mark,0,sizeof(mark));//清空,重复使用 int r=0; Next[1]=Next[0]=0; for (int j=1; j<=z; j++) //TB智慧DNA片段T { int x; cin>>x; if (mark[x]) b[j]=j-mark[x];//计算大小 else b[j]=0; mark[x]=j; if (j>1)//Next的预处理 { while (r>0&&b[r+1]!=b[j]) r=Next[r];//当b[r+1]和b[j]不匹配时,减小r if (b[r+1]==b[j]) r++; Next[j]=r; } } memset(mark,0,sizeof(mark)); r=0; for (int j=1; j<=y; j++)//KMP { int x=a[j]; if (j-a[j]<j-r) x=0; while (r&&b[r+1]!=x) { r=Next[r]; if (j-a[j]<j-r) x=0; } if (b[r+1]==x) r++; if (r==z) ans++,pos[ans]=j-r+1,r--; } cout<<ans<<endl; for (int j=1; j<=ans; j++) cout<<pos[j]<<" ",pos[j]=0; cout<<endl; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); ans=0; } }
感谢baijiu支持