[二分][后缀数组]Substrings POJ1226

23 篇文章 0 订阅
22 篇文章 0 订阅

You are given a number of case-sensitive strings of alphabetic characters, find the largest string X, such that either X, or its inverse can be found as a substring of any of the given strings.

Input

The first line of the input contains a single integer t (1 <= t <= 10), the number of test cases, followed by the input data for each test case. The first line of each test case contains a single integer n (1 <= n <= 100), the number of given strings, followed by n lines, each representing one string of minimum length 1 and maximum length 100. There is no extra white space before and after a string.

Output

There should be one line per test case containing the length of the largest string found.

Sample Input

2
3
ABCD
BCDFF
BRCD
2
rose
orchid

Sample Output

2
2 

题意: 给出n个字符串,求一个最长公共子串长度,这里的公共既可以回文出现,也可以正常出现。

分析: 类似求n个字符串最长公共子串的做法,不过连接字符串的时候需要把回文的串也连进去,之后就是二分子串长度的常规做法了。不过这题有一些坑,首先给出的字符串中可能存在数字,但题目中只说会存在字母!而且对于只有一个字符串的情况还需要特判一下,不过这题数据比较水,没特判也是可以过的。

具体代码如下:  

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

const int maxn = 1e5+10;
int n, m, num_str;
int s[maxn];
int sa[maxn], height[maxn], x[maxn], y[maxn], rk[maxn], tong[maxn], str[maxn];
char st[maxn];

void get_sa()
{
	for(int i = 0; i <= m; i++) tong[i] = 0;
	for(int i = 0; i <= 2*n; i++) y[i] = x[i] = 0;
    for(int i = 1; i <= n; i++) tong[x[i] = s[i]] ++;
    for(int i = 2; i <= m; i++) tong[i] += tong[i-1];
    for(int i = n; i; i--) sa[tong[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) continue;
            y[++num] = sa[i] - k;
        }
        for(int i = 0; i <= m; i++) tong[i] = 0;
        for(int i = 1; i <= n; i++) tong[x[i]]++;
        for(int i = 2; i <= m; i++) tong[i] += tong[i-1];
        for(int i = n; i; i--) sa[tong[x[y[i]]]--] = y[i], y[i] = 0;
    	for(int i = 0; i <= 2*num; i++)
    	{
    		int temp = x[i];
    		x[i] = y[i];
    		y[i] = temp;
		}
        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]) == 1 ? num : ++ num;
        if(n == num) return;
        m = num;
    }
}

void get_height() 
{
    for(int i = 1; i <= n; i++) rk[sa[i]] = i;
    for(int i = 1, k = 0; i <= n; i++) 
	{
        if(rk[i] == 1) continue;
        if(k) k--;
        int j = sa[rk[i]-1];
        while(i + k <= n && j + k <= n && s[i+k] == s[j+k]) k++;
        height[rk[i]] = k;
    }
}

bool check(int x)
{
	vector<int> a[105];
	bool flag = false;
	for(int i = 1; i <= n; i++)
	{
		if(height[i] >= x)
		{
			if(!flag)
			{
				flag = true;
				a[str[sa[i-1]]].push_back(sa[i-1]);
			}
			a[str[sa[i]]].push_back(sa[i]);
		}
		if(height[i] < x || i == n)
		{
			flag = false;
			for(int i = 1; i <= num_str; i++)
			{
				if(!a[i].size())
					break;
				if(i == num_str)
					return true;
			}
			for(int i = 1; i <= num_str; i++)
				a[i].clear();
		}
	}
	return false;
}

void solve() 
{ 
    get_sa();
    get_height();
	int l = 1, r = 105, ans = -1;//二分符合题意的最长长度 
	while(l <= r)
	{
		int m = l+r>>1;
		if(check(m))
		{
			ans = m;
			l = m+1;
		}
		else
			r = m-1;
	}
	if(ans == -1)
		cout << 0 << endl;
	else
		cout << ans << endl;
}

signed main()
{
	int T;
	cin >> T;
	while(T--) 
	{
		cin >> num_str;
		if(num_str == 1)
		{
			scanf("%s", st+1);
			printf("%d\n", strlen(st+1));
			continue;
		}
		int len = 0;
		int cnt = 0;
		for(int i = 1; i <= num_str; i++)
		{
			scanf("%s", st+1);
			int lent = strlen(st+1);
			for(int j = len+1; j <= lent+len; j++)
				s[j] = st[j-len]-'0'+1, str[j] = i;
			len += lent;
			s[++len] = 100+(++cnt);
			for(int j = len+1; j <= lent+len; j++)
				s[j] = st[lent-(j-len)+1]-'0'+1, str[j] = i;
			len += lent;
			s[++len] = 100+(++cnt);
		}
		n = len;
		m = 400;
		solve();
	}
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值