您的位置:首页 > 其它

poj 3167 (kmp 好题)

2013-10-12 00:32 337 查看
题目:http://poj.org/problem?id=3167

题目大意:给你两个数列a、b,长度分别为 n 、k,让你用 b 去匹配 a,只要是两者的相对大小对的上就算匹配,让你求出所有的

匹配的位置。

思路:KMP好题!kmp 是顺序匹配的,建设前面已经匹配了,现在正在匹配 i 和 j ,因为是相对大小,如果i位置的前面比a[ i ]的

数目和 j 位置比b[ j ] 小的数目相等而且i位置的前面与a[ i ]相等的数目和 j 位置比b[ j ] 相等的数目相等,那么就是可以匹配。

把 while 里面相等的那句话换一下,直接kmp就好了。基本的思想就是这样,实现上面那个的方法貌似有很多种,由于s比较小,就直接

暴力上了。其他的明天再看,这里有个博客,说的不错:http://www.cppblog.com/zxb/archive/2010/10/06/128782.aspx?opt=admin,还有用树状数组统计排名的,也可以用枚举法。。。 = = 
唉,这道题真是伤,一直WA,检查到半夜,终于发现,原来是把 while 写成 if 了,唉,无语啊。。。。 T^T

代码如下:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

int n,k,s;

struct TT
{
int len;
int num[111111];
int low[111111][33],equ[111111][33];
} t,p;

void init(TT &tmp)
{
for(int i =1;i<=tmp.len;i++)
{
for(int j = 1;j<=s;j++)
{
tmp.low[i][j] = tmp.low[i-1][j] + (tmp.num[i] < j ? 1 : 0);
tmp.equ[i][j] = tmp.equ[i-1][j] + (tmp.num[i] == j ? 1 : 0);
}
}
}

int fail[25555];

int check(TT &a,TT &b,int i,int j)
{
if(a.low[i][a.num[i]] - a.low[i - j][a.num[i]] == b.low[j][b.num[j]]
&& a.equ[i][a.num[i]] - a.equ[i - j][a.num[i]] == b.equ[j][b.num[j]])
return 1;
return 0;
}

void get_fail()
{
fail[1] = 0;
int j = 0;
for(int i = 2;i<=k;i++)
{
if(j >= 1 && !check(p,p,i,j+1) )
j = fail[j];
if(check(p,p,i,j+1)) j++;
fail[i] = j;
}
}

vector <int> ans;

void kmp()
{
ans.clear();
get_fail();
int j = 0;
for(int i = 1;i<=n;i++)
{
while(j >= 1 && !check(t,p,i,j+1)) j = fail[j];
if(check(t,p,i,j+1)) j++;

if(j == k)
{
ans.push_back(i-k+1);
j = fail[j];
}
}
}

int main()
{
while(~scanf("%d%d%d",&n,&k,&s))
{
for(int i = 1;i<=n;i++)
scanf("%d",&t.num[i]);
for(int i = 1;i<=k;i++)
scanf("%d",&p.num[i]);
t.len = n;
p.len = k;
init(t);
init(p);
kmp();
printf("%d\n",ans.size());
for(int i = 0;i<ans.size();i++)
printf("%d\n",ans[i]);
}
return 0;
}


这个是树状数组版本,用树状数组看当前待匹配的值的排名,记当前匹配的是i和j+1,先把 i 这个值放进树状数组,看能不能匹配,如果

可以,那么这个值就插进去,不可以,记 len = j - fail[ j ],把 i 位置结尾的长度为 j+1 的字符串的前 len 个减掉。这里判断如果

j == k 时,也就是匹配找到一个答案时,也是要减掉这前面 len 个长度,不过前面开始是 i - j + 1,因为 j 之前 ++ 了,因为这个 WA 

了几次。。。 = =
树状数组版本代码如下:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

int n,k,s;

struct TT
{
int len;
int num[111111];
int low[111111],equ[111111];
} t,p;

int c[33];

int lowbit(int x)
{
return x&(-x);
}

void update(int x,int val)
{
while(x <= s)
{
c[x] += val;
x += lowbit(x);
}
}

int sum(int x)
{
int cnt = 0;
while(x > 0)
{
cnt += c[x];
x -= lowbit(x);
}
return cnt;
}

void init(TT &tmp)
{
memset(c,0,sizeof(c));
for(int i =1;i<=tmp.len;i++)
{
update(tmp.num[i],1);
tmp.low[i] = sum(tmp.num[i]-1);
tmp.equ[i] = sum(tmp.num[i]) - sum(tmp.num[i]-1);
//printf("i = %d,low = %d,equ = %d\n",i,tmp.low[i],tmp.equ[i]);
}
}

int fail[25555];

void get_fail()
{
memset(c,0,sizeof(c));
fail[1] = 0;
int j = 0;
for(int i = 2;i<=k;i++)
{
update(p.num[i],1);
while(j >= 1 && !(sum(p.num[i]-1) == p.low[j+1] && sum(p.num[i]) - sum(p.num[i] - 1) == p.equ[j+1]))
{
for(int kk = i - j;kk < i - fail[j];kk++)
update(p.num[kk],-1);
j = fail[j];
}
if(sum(p.num[i]-1) == p.low[j+1] && sum(p.num[i]) - sum(p.num[i] - 1) == p.equ[j+1])
{
j++;
}
else update(p.num[i],-1);
fail[i] = j;
//printf("i = %d,fail = %d\n",i,fail[i]);
}
}

vector <int> ans;

void kmp()
{
ans.clear();
get_fail();
int j = 0;
memset(c,0,sizeof(c));
for(int i = 1;i<=n;i++)
{
update(t.num[i],1);
while(j >= 1 && !(sum(t.num[i]-1) == p.low[j+1] && sum(t.num[i]) - sum(t.num[i] - 1) == p.equ[j+1]))
{
for(int kk = i - j ;kk < i - fail[j];kk++)
update(t.num[kk],-1);
j = fail[j];
}
if(sum(t.num[i]-1) == p.low[j+1] && sum(t.num[i]) - sum(t.num[i] - 1) == p.equ[j+1])
{
j++;
}
else update(t.num[i],-1);
if(j == k)
{
ans.push_back(i-k+1);
for(int kk = i - j + 1;kk <= i - fail[j];kk++)
update(t.num[kk],-1);
j = fail[j];
}
}
}

int main()
{
while(~scanf("%d%d%d",&n,&k,&s))
{
for(int i = 1;i<=n;i++)
scanf("%d",&t.num[i]);
for(int i = 1;i<=k;i++)
scanf("%d",&p.num[i]);
t.len = n;
p.len = k;
//init(t);
init(p);
kmp();
printf("%d\n",ans.size());
for(int i = 0;i<ans.size();i++)
printf("%d\n",ans[i]);
}
return 0;
}


下面这个版本是根据上面那个博客来的,思路很好,代码如下:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

int n,k,s;

struct TT
{
int len;
int num[111111];
int low[111111],occ[33],hig[111111];
} t,p;

void init(TT &tmp)
{
memset(tmp.occ,-1,sizeof(tmp.occ));
tmp.occ[0] = -2;
tmp.occ[s+1] = -2;
for(int i =1;i<=tmp.len;i++)
{
if(tmp.occ[tmp.num[i]] == -1)
tmp.occ[tmp.num[i]] = i;
int j;
for(j = tmp.num[i] - 1;tmp.occ[j] == -1;j--);
tmp.low[i] = tmp.occ[j];
for(j = tmp.num[i] + 1;tmp.occ[j] == -1;j++);
tmp.hig[i] = tmp.occ[j];
}
}

int check(int* a,int* b,int pos)
{
if(a[p.occ[b[pos]]] != a[pos]) return 0;
if(p.low[pos] >= 0 && a[p.low[pos]] >= a[pos]) return 0;
if(p.hig[pos] >= 0 && a[p.hig[pos]] <= a[pos]) return 0;
return 1;
}

int fail[25555];

void get_fail()
{
fail[1] = 0;
int j = 0;
for(int i = 2;i<=k;i++)
{
while(j >= 1 && !check(p.num + (i - j - 1),p.num,j+1))
{
j = fail[j];
}
if(check(p.num + (i - j - 1),p.num,j+1))
{
j++;
}
fail[i] = j;
//printf("i = %d,fail = %d\n",i,fail[i]);
}
}

vector <int> ans;

void kmp()
{
ans.clear();
get_fail();
int j = 0;
for(int i = 1;i<=n;i++)
{
while(j >= 1 && !check(t.num + (i - j - 1),p.num,j+1))
{
j = fail[j];
}

if(check(t.num + (i - j - 1),p.num,j+1))
{
j++;
}
//printf("i = %d,j = %d\n",i,j);
if(j == k)
{
ans.push_back(i-k+1);
j = fail[j];
}
}
}

int main()
{
while(~scanf("%d%d%d",&n,&k,&s))
{
for(int i = 1;i<=n;i++)
scanf("%d",&t.num[i]);
for(int i = 1;i<=k;i++)
scanf("%d",&p.num[i]);
t.len = n;
p.len = k;
//init(t);
init(p);
kmp();
printf("%d\n",ans.size());
for(int i = 0;i<ans.size();i++)
printf("%d\n",ans[i]);
}
return 0;
}


还有一个枚举法,博客地址:http://www.mzry1992.com/blog/miao/%E3%80%90poj-3167%E3%80%91cow-patterns.html

依次递增枚举 p 中的数,然后匹配,整个过程都要保证匹配是递增的,复杂度O(s*s*n)。

好屌,这样都可以。。 这个方法,反正我是想不出来。。 = =

枚举代码如下:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

int n,k,s;

int p[25005],t[111111];

int tmp_p[25005],tmp_t[111111];

int fail[25005];

void get_fail()
{
int j = 0;
fail[1] = 0;
for(int i = 2;i<=k;i++)
{
while(j >= 1 && tmp_p[i] != tmp_p[j+1]) j =fail[j];
if(tmp_p[i] == tmp_p[j+1]) j++;
fail[i] = j;
}
}

int tot;

int cnt[111111],match[111111];

void kmp(int cur)
{
for(int i = 1;i<=n;i++)
if(t[i] == cur) tmp_t[i] = 1;
else tmp_t[i] = 0;
int j = 0;
for(int i = 1;i<=n;i++)
{
while(j >= 1 && tmp_t[i] != tmp_p[j+1]) j = fail[j];
if(tmp_t[i] == tmp_p[j+1]) j++;
if(j == k)
{
int beg = i - j + 1;
if(match[beg] < cur && cnt[beg] <= tot)
{
cnt[beg] ++;
match[beg] = cur;
}
j = fail[j];
}
}
}

vector <int> ans;

int occ[33];

int main()
{
while(~scanf("%d%d%d",&n,&k,&s))
{
memset(occ,0,sizeof(occ));
for(int i = 1;i<=n;i++) scanf("%d",&t[i]);
for(int i = 1;i<=k;i++) scanf("%d",&p[i]),occ[p[i]] = 1;
tot = 0;
memset(match,0,sizeof(match));
memset(cnt,0,sizeof(cnt));
for(int i = 1;i<=s;i++)
{
if(!occ[i]) continue;
for(int j = 1;j<=k;j++)
if(p[j] == i) tmp_p[j] = 1;
else tmp_p[j] = 0;
get_fail();
for(int j = 1;j<=s;j++) kmp(j);// p 中 i 和 t 中的 j 相匹配
tot++;
}
ans.clear();
for(int i = 1;i<=n;i++)
{
//printf("i = %d,cnt = %d\n",i,cnt[i]);
if(cnt[i] == tot)
ans.push_back(i);
}
printf("%d\n",ans.size());
for(int i = 0;i<ans.size();i++)
printf("%d\n",ans[i]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: