백준10840 구간 성분

문제 링크

  • http://icpc.me/10840

문제 출처

  • 2015 KOI 전국 본선 고등부2

사용 알고리즘

  • Hash

시간복잡도

  • 평균 O(N2 + M2)

풀이

가장 Naive한 풀이는 모든 경우를 단순 for문으로 돌려보는 것이고, O(N2M2)정도 걸립니다.
N과 M 모두 최대 1500이기 때문에 조금 더 효율적인 방법을 생각해봅시다.

특정 구간에 알파벳들이 각각 몇 개씩 있는지를 판단하는 것이 핵심입니다. 각각의 알파벳을 서로 다른 소수와 매칭을 시킨 뒤, 구간에 속해있는 알파벳에 따라 곱해주는 방식의 해시를 사용하면 쉽게 풀릴 것 같습니다. 바로 구현을 해봅시다.

소수를 적당한 개수만큼 구합시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
vector<int> prime;

bool isPrime(int n){
	if(n < 2) return 0;
	for(int i=2; i*i<=n; i++) if(n%2 == 0) return 0;
	return 1;
}

void getPrime(){
	for(int i=2; i<=10000; i++){
		if(isPrime(i)) prime.push_back(i);
	}
}

소수를 최대 1500개를 곱해야 합니다. 숫자가 너무 커지기 때문에 적당한 소수로 나머지 연산을 해줍시다. const int mod = 524287;
해시 충돌을 방지하기 위해 해시값을 두 개정도 생성하겠습니다.
해시 테이블을 만들어봅시다.

1
2
typedef pair<int, int> p;
vector<p> Hash[mod];

vector[첫 번째 해시값] = {두 번째 해시값, 구간 길이} 형태로 저장하겠습니다.

첫 번째 문자열에 있는 모든 구간에 대해 해시값을 구해줍시다.
x에는 첫 번째 해시값, y에는 두 번째 해시값이 들어갑니다.

1
2
3
4
5
6
7
8
9
10
11
12
string a; cin >> a;
int x, y;
for(int i=0; i<a.size(); i++){
  x = y = 1;
  for(int j=i; j<a.size(); j++){
    int now = a[j] - 'a';
    int len = j - i + 1;
    x = (x * prime[now]) % mod;
    y = (y * prime[now + 26]) % mod;
    Hash[x].push_back({y, len});
  }
}

두 번째 문자열에 있는 모든 구간에 대해 해시값을 구한 뒤, 해시 테이블에 있는지 판단해주면서 정답을 구합시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string b; cin >> b;
int ans = 0;
for(int i=0; i<b.size(); i++){
  x = y = 1;
  for(int j=i; j<b.size(); j++){
    int now = b[j] - 'a';
    int len = j - i + 1;
    x = (x * prime[now]) % mod;
    y = (y * prime[now + 26]) % mod;
    bool flag = 0;
    for(int i=0; i<Hash[x].size(); i++)
      if(Hash[x][i] == make_pair(y, len)) flag = 1;
    if(flag) ans = max(ans, len);
  }
}

전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <bits/stdc++.h>
using namespace std;

vector<int> prime;
const int mod = 524287;
typedef pair<int, int> p; //hash, len

bool isPrime(int n){
	if(n < 2) return 0;
	for(int i=2; i*i<=n; i++) if(n%2 == 0) return 0;
	return 1;
}

void getPrime(){
	for(int i=2; i<=10000; i++){
		if(isPrime(i)) prime.push_back(i);
	}
}

vector<p> Hash[mod];


void solve(string &a, string &b){
	int x, y;
	for(int i=0; i<a.size(); i++){
		x = y = 1;
		for(int j=i; j<a.size(); j++){
			int now = a[j] - 'a';
			int len = j - i + 1;
			x = (x * prime[now]) % mod;
			y = (y * prime[now + 26]) % mod;
			Hash[x].push_back({y, len});
		}
	}

	int ans = 0;
	for(int i=0; i<b.size(); i++){
		x = y = 1;
		for(int j=i; j<b.size(); j++){
			int now = b[j] - 'a';
			int len = j - i + 1;
			x = (x * prime[now]) % mod;
			y = (y * prime[now + 26]) % mod;
            bool flag = 0;
            for(int i=0; i<Hash[x].size(); i++) if(Hash[x][i] == make_pair(y, len)) flag = 1;
			if(flag) ans = max(ans, len);
		}
	}
	cout << ans;
}

int main(){
	getPrime();
	string a, b;
	cin >> a >> b;
	solve(a, b);
}