POJ 3294 出现在至少K个字符串中的子串

在掌握POJ 2774(两个串求最长公共子串)以及对Height数组分组后,本题还是容易想出思路的。

首先用字符集外的不同字符连接所有串,这是为了防止两个后缀在比较时超过某个字符串的分界。二分子串的长度,扫描height数组,判定是否有某个分组来源与至少K个原字符串(本题要求出现超过n的一半次)。

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <string.h>
#include <stdio.h>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <cassert>
#include <sstream>
using namespace std;

const int N=2e6+110;

int sa[N];
int t1[N],t2[N],c[N];
int rk[N],height[N];

inline int cmp(int *r,int a,int b,int l){
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
char s[N];
void calcSA (char *s,int n,int m) {
    int i,j,p,*x=t1,*y=t2;
    for(i=0;i<m;i++)c[i]=0;
    for(i=0;i<n;i++)c[x[i]=s[i]]++;
    for(i=1;i<m;i++)c[i]+=c[i-1];
    for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
    for(j=1;j<=n;j<<=1){
        p=0;
        for(i=n-j;i<n;i++)y[p++]=i;
        for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; // 排名从小到大,如果pos比j大,则suffix(sa[i]-j)的第二关键字为p
        for(i=0;i<m;i++)c[i]=0;
        for(i=0;i<n;i++)c[x[y[i]]]++;
        for(i=1;i<m;i++)c[i]+=c[i-1];
        for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; // 根据第二关键字从大到小,确定新一轮sa
        swap(x,y);
        p=1;x[sa[0]]=0;
        for(i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p>=n)break;
        m=p;
    }
}

void calcHeight(char *s,int n) {
    int i,j,k=0;
    for(i=0;i<=n;i++)rk[sa[i]]=i;
    for(i=0;i<n;i++){
        if(k)k--; // h[i]>=h[i-1]-1
        j=sa[rk[i]-1]; // suffix(j)排名在suffix(i)前一位
        while(s[i+k]==s[j+k])k++; // 暴力计算lcp
        height[rk[i]]=k;
    }
}

int belong[N];

vector<int>ans;
bool vis[128];
bool ok(int n,int m,int k) {
    memset(vis,0,sizeof vis);
    int cnt=1;
    vis[belong[sa[1]]]=true;
    vector<int>ret;
    bool push=false;
    for (int i=2;i<=n;i++) {
        if (height[i]<m) {
            memset(vis,0,sizeof vis);
            push=false;
            vis[belong[sa[i]]]=true;
            cnt=1;
        }
        else if (!push){
            if (!vis[belong[sa[i]]]) {
                vis[belong[sa[i]]]=true;
                ++cnt;
            }
            if (cnt>k/2&&!push) {
                push=true;
                ret.push_back(sa[i]);
            }
        }
    }
    //cout<<"go "<<m<<" "<<ret.size()<<endl;
    if (ret.size()>0) {
        ans=ret;
        return true;
    }
    else return false;
}
int main () {
    int n;
    while (scanf("%d",&n)!=EOF,n) {
        int p=0;
        int maxLen=0;
        for (int i=1;i<=n;i++) {
            scanf("%s",s+p);
            int l=strlen(s+p);
            maxLen=max(maxLen,l);
            int np=p+l;
            for (int j=p;j<np;j++) {
                belong[j]=i;
                s[j]+=5; // 这里+5是为了保证插入的分隔符不在字符集中出现,n至多为100,a的ASCII为97
            }
            belong[np]=111;
            p=np;
            s[p++]=i;
        }
        s[--p]=0;
        belong[p]=-1;
        calcSA(s,p+1,550);
        calcHeight(s,p);
        int l=0,r=maxLen,ret=0;
        while (l<=r) {
            int m=(l+r)>>1;
            if (ok(p,m,n)) {
                ret=m;
                l=m+1;
            }
            else
                r=m-1;
        }
        if (ret==0) {
            puts("?\n");
        }
        else {
            for (int i=0;i<ans.size();i++) {
                int beg=ans[i];
                for (int j=0;j<ret;j++) printf("%c",s[beg+j]-5);
                puts("");
            }
            puts("");
        }
    }
    return 0;
}
时间: 2024-10-13 21:00:30

POJ 3294 出现在至少K个字符串中的子串的相关文章

POJ 3294 后缀数组:求不小于k个字符串中的最长子串

思路:先把所有的串连接成一个串,串写串之前用没出现过的字符隔开,然后求后缀:对height数组分组二分求得最长的公共前缀,公共前缀所在的串一定要是不同的,不然就不是所有串的公共前缀了,然后记下下标和长度即可. 刚开始理解错题意,然后不知道怎么写,然后看别人题解也不知道怎么意思,后面看了好久才知道题目意思理解错了. 时间四千多ms,别人才一百多ms,不知道别人怎么做的-- #include<iostream> #include<cstdio> #include<cstring&

spoj 694 求一个字符串中不同子串的个数

SPOJ Problem Set (classical) 694. Distinct Substrings Problem code: DISUBSTR Given a string, we need to find the total number of its distinct substrings. Input T- number of test cases. T<=20; Each test case consists of one string, whose length is <=

字符串-02. 删除字符串中的子串(20)

输入2个字符串S1和S2,要求删除字符串S1中出现的所有子串S2,即结果字符串中不能包含S2. 输入格式: 输入在2行中分别给出不超过80个字符长度的.以回车结束的2个非空字符串,对应S1和S2. 输出格式: 在一行中输出删除字符串S1中出现的所有子串S2后的结果字符串. 输入样例: Tomcat is a male ccatat cat 输出样例: Tom is a male import java.util.Scanner; public class Main { public static

删除字符串中的子串

输入2个字符串S1和S2,要求删除字符串S1中出现的所有子串S2,即结果字符串中不能包含S2. 输入格式: 输入在2行中分别给出不超过80个字符长度的.以回车结束的2个非空字符串,对应S1和S2. 输出格式: 在一行中输出删除字符串S1中出现的所有子串S2后的结果字符串. 输入样例: Tomcat is a male ccatat cat 输出样例: Tom is a male #include<stdio.h> #include<string.h> int main(){ cha

PAT 字符串-02 删除字符串中的子串

1 /* 2 2 *PAT 字符串-02 删除字符串中的子串 3 3 *2015-08-09 4 4 作者:flx413 5 5 */ 6 7 #include<stdio.h> 8 #include<string.h> 9 10 void fun(char *str, char *substr) { 11 int i, j, k, flag; 12 int len = strlen(substr); 13 14 while(strstr(str, substr)) { //如果字

C++ 在字符串中插入子串+推断字符串是否由空格组成

// Example3.cpp : 定义控制台应用程序的入口点. #include "StdAfx.h" #include <string> #include <iostream> using namespace std; int main(void) { string str,str1,str2; int index; //推断截取的子串是否由blanks组成 str=" cjc is a master."; str1="cjc

字符串中数字子串的求和

字符串中数字子串的求和 题目: 给定一个字符串str,求其中全部数字串所代表的数字之和. 要求: 1.忽略小数点字符,例如"A1.3",其中包含两个数字1和3. 2.如果紧贴数字子串的左侧出现字符'-',当连续出现次数为奇数时, 则数字视为负,连续出现的数量为偶数时,则数字视为正. 例如:"A-1BC--12",其中包含数字为-1和12. 例: str = "A1CD2E33" , 返回36 str = "A-1B--2C--D6E&q

08-在字符串中统计子串出现的次数

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace _05在字符串中统计子串出现的次数 { class Program { static void Main(string[] args) { int count = SubstringCount("qhwerzdfrefysasefh

C++ 在字符串中插入子串+判断字符串是否由空格组成

// Example3.cpp : 定义控制台应用程序的入口点. #include "StdAfx.h" #include <string> #include <iostream> using namespace std; int main(void) { string str,str1,str2; int index; //判断截取的子串是否由blanks组成 str=" cjc is a master."; str1="cjc