백준 - 구간 성분(10840번 - KOI)

 링크

라빈-카프 알고리즘을 살짝 변형하고, prefix sum을 이용해 선형 탐색으로 풀이했다.

모든 알파벳에 대한 해시 값을 다음과 같이 두었다.

a = 1, b = 2, c = 3, d = 4 ...

즉, aba = 1 + 2 + 1 = 4, baa = 2 + 1 + 1 이 되어 자리가 다르더라도 총 해시값이 같다면 문제의 조건을 충족하게 된다.

하지만 bb = 2 + 2 = 4 == aba 처럼 문자열이 다르더라도 해시값이 같을 수 있기 때문에 충돌을 처리해 주어야 한다.


먼저, 문자열1에 대해 라빈-카프 알고리즘과 같은 방식으로 길이가 m 인 부분 문자열들에 대해 모든 H 값을 구한다. (단, 길이가 가장 길어야 하므로 m은 최대 길이부터 감소한다.)

그리고 구해진 H 값들(50000을 넘지 않는다 -> 27 * 1500) 의 시작 index를 체크 배열에 push 한다.

다음으로 문자열2에 대해 같은 방식으로 H를 구해 나가는데 문자열1에서 H가 나왔었다면 -> 충돌이므로 두 부분 문자열을 검사한다.


이 때 부분 문자열을 일일히 검사하려면 N^2 의 시간이 걸릴 수 있으므로 문자열1, 문자열2에 대해 각 자리마다의 알파벳 수 prefix sum을 구해 놓고, 두 부분 문자열의 구간에 대해 26번 반복해주면 두 부분 문자열이 일치하는지 여부를 알아낼 수 있다.


#include<iostream>
#include<memory.h>
#include<algorithm>
#include<math.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define FIO ios_base::sync_with_stdio(false); cin.tie(NULL)
#define pii pair<int, int>
#define pdd pair<double, double>
#define pic pair<int, char>
#define ll long long
#define pll pair<ll, ll>
#define vi vector<int>
#define vl vector<long long>
#define vc vector<char>
#define vii vector<pii>
#define IMAX 2000000001
#define LMAX 1000000000000000000
#define DMAX 0xFFFFFFFFFFFFF
#define MOD 100003
int mv1[4] = { 0, 0, 1, -1 };
int mv2[4] = { 1, -1, 0, 0 };
int mv_all1[8] = { 0, 1, 0, -1, -1, -1, 1, 1 };
int mv_all2[8] = { 1, 0, -1, 0 , -1, 1, -1, 1 };

int sum1[1501][26];
int sum2[1501][26];
vector<int> visit[50000];

int main(void) {
	FIO;

	string str1;
	string str2;

	cin >> str1 >> str2;

	for (int i = 1; i <= str1.size(); i++)
	{
		for (int j = 0; j < 26; j++)
		{
			if (j == str1[i - 1] - 'a') {
				sum1[i][j] = sum1[i - 1][j] + 1;
			}
			else {
				sum1[i][j] = sum1[i - 1][j];
			}
		}
	}

	for (int i = 1; i <= str2.size(); i++)
	{
		for (int j = 0; j < 26; j++)
		{
			if (j == str2[i - 1] - 'a') {
				sum2[i][j] = sum2[i - 1][j] + 1;
			}
			else {
				sum2[i][j] = sum2[i - 1][j];
			}
		}
	}
	//두 문자열에 대해 각 자리수마다 알파벳들의 개수를 구함.

	int ans = 0;

	for (int mid = min(str1.size(), str2.size()); mid > 0; mid--) {

		int H = 0;
		for (int i = 0; i < 50000; i++)
		{
			visit[i].clear();
		}

		for (int i = 0; i <= str1.size() - mid; i++)
		{
			if (i == 0) {

				for (int j = 0; j < mid; j++)
				{
					H += (str1[j] - 'a' + 1);
				}
			}
			else {
				H = H - (str1[i - 1] - 'a' + 1) + (str1[i + mid - 1] - 'a' + 1);
			}

			visit[H].push_back(i);
		}

		H = 0;

		bool find = false;

		for (int i = 0; i <= str2.size() - mid; i++)
		{
			if (i == 0) {

				for (int j = 0; j < mid; j++)
				{
					H += (str2[j] - 'a' + 1);
				}
			}
			else {
				H = H - (str2[i - 1] - 'a' + 1) + (str2[i + mid - 1] - 'a' + 1);
			}

			if (visit[H].size()) {
				//해시 충돌

				for (int j = 0; j < visit[H].size(); j++)
				{
					bool match = true;

					for (int k = 0; k < 26; k++)
					{
						//두 부분 문자열의 구간마다 알파벳의 개수가 일치하는지 확인
						if (sum1[visit[H][j] + mid][k] - sum1[visit[H][j]][k] != sum2[i + mid][k] - sum2[i][k]) {
							match = false;
							break;
						}
					}
					if (match) {

						find = true;
						break;
					}
				}
			}

			if (find)
				break;
		}

		if (find) {
			ans = mid;
			break;
		}
	}

	cout << ans;
}

댓글

이 블로그의 인기 게시물

HTML - input file 버튼 꾸미기

HTML - 이미지 미리보기(jQuery 없이)

BOJ - DNA 유사도(2612)