본문 바로가기

알고리즘

algospot :: RESTORE 실험 데이터 복구하기

반응형

문제 : https://algospot.com/judge/problem/read/RESTORE

알고리즘 문제 해결 전략 1권 p.329


[알고리즘 STEP. 1]

결국 주어진 문자열 조각들을 모두 조합해서 이어보면서 총 길이가 최소가 되는 (= 겹치는 부분이 최대가 되는) 문자열을 출력하면 된다. 

이는 외판원문제 (http://int-num.tistory.com/18)와 거의 똑같은 알고리즘이다. 거리 합의 최소를 반환하던걸 (겹치는)길이 합의 최대를 반환하도록 바꾸기만 하면 된다.


먼저 주어진 문자열 조각들의 겹치는 부분들의 길이를 먼저 구해서 행렬에 저장한다.


*int calcCommon(int i , int j)

i번째 문자열 뒤에  j번째 문자열을 붙일 때, 겹치는 길이를 반환한다.


1) i번째 문자열이 j번째 문자열 보다 길다면? 예를들어, [i 문자열] saoji [j 문자열] jing 이라하자.


이 때는 i문자열의 마지막 끝에 j문자열의 마지막 끝을 맞춘 상태로 시작해서

j를 하나씩 오른쪽으로 옮기며 겹치는 길이를 하나씩 줄여가며 최대 몇개의 문자를 겹칠 수 있는지 구한다.




1) j번째 문자열이 i번째 문자열 보다 길다면? 예를들어, [i 문자열] oji [j 문자열] jing 이라하자.


이 때는 j문자열의 처음부분에 i문자열을 맞춘 상태로 시작해서

j를 하나씩 오른쪽으로 옮기며 겹치는 길이를 하나씩 줄여가며 최대 몇개의 문자를 겹칠 수 있는지 구한다.




*int calcCommon(int i , int j)

모든 i, j에 대해 string i 뒤에 string j를 이어 붙일 때의 겹치는 길이를 구해서 commonLength[i][j]에 저장한다.



int calcCommon(int i , int j)



{
for(int common = min(piece[j].length(),piece[i].length()); common >0; common--){
FOR(index, common){
if(piece[i][piece[i].length()-common+index] != piece[j][index]) break;
if(index == common-1) return common;
}
}
return 0;

}
void precalc(){
//string i 뒤에 string j를 이어붙일 때 겹치는 길이
FOR(i, k){
FOR(j, k){
if(i==j) continue;
else commonLength[i][j] = calcCommon(i, j);
}
}

} 



이제 이 commonLength 행렬을 가지고 외판원 문제를 풀 듯 문제를 풀면 된다.


다음과 같이 알고리즘을 짜면, 주어진 문자열을 이어붙일때 겹칠 수 있는 최대 길이가 출력된다.


//https://algospot.com/judge/problem/read/RESTORE

#include <iostream>
#include <vector>
#include <cstring>
#include <limits>
#define FOR(i, ed) for(int i = 0 ; i < ed; i++)
#define MINF std::numeric_limits<int>::min(); //*
using namespace std;

// input
int tc, k;
vector<string> piece;
//algorithm
int commonLength[16][16];
int cache[16][1<<16]; //j상태일 때 i에서부터 얻을수 있는 최대

int calcCommon(int i , int j){
for(int common = min(piece[j].length(),piece[i].length()); common >0; common--){
FOR(index, common){
if(piece[i][piece[i].length()-common+index] != piece[j][index]) break;
if(index == common-1) return common;
}
}
return 0;

}
void precalc(){
//string i 뒤에 string j를 이어붙일 때 겹치는 길이
FOR(i, k){
FOR(j, k){
if(i==j) continue;
else commonLength[i][j] = calcCommon(i, j);
}
}
}

int play(int here, int visited){
if(visited == (1<<k)-1) return 0;
int &ret = cache[here][visited];
if(ret >0) return ret;
ret = MINF;
FOR(next, k){
if(visited & (1<<next)) continue;
ret = max(ret, commonLength[here][next]+play(next, visited+(1<<next))); // *
}
return ret;
}


int main(){
// freopen("input.txt", "r", stdin);
cin >> tc;
while(tc--){
piece.clear();
memset(commonLength, 0, sizeof(commonLength));
memset(cache, -1, sizeof(cache));
cin >> k;
FOR(i, k){
string temp;
cin >> temp;
piece.push_back(temp);
}
cleansubstr();
// print(piece);
precalc();
// printcommonLength();
int rslt = MINF;
int start;
FOR(i, k){
int temp = play(i, 1<<i);
if(rslt <= temp){
rslt = temp;
start = i;
}
}
cout << play(start, 1<<start) << endl;
}
}


 



[알고리즘 STEP. 2]

문제에서 출력하고자 하는건 문자열 길이가 아니라 문자열 그 자체이다. 따라서 위에서 채워진 cache배열을 바탕으로 문자열을 재구성해야 한다.

현재 문자열과 지금까지 사용한 문자열들의 집합이 주어질 때, 
앞으로 겹칠 수 있는 길이의 최댓값 = 남은 문자열 중 i문자열을 이었을 때의 겹치는 길이 + i문자열부터 나머지를 이용해서 만드는 문자열들의 겹치는 길이
라면 다음 선택은 i문자열 이라는 것을 알 수 있다.

이를 이용해서 문자열을 재구성 하는 함수는 다음과 같다.

string reconstruct(int here, int visited){
if(visited == (1<<k)-1) return "";
FOR(next, k){
if(visited & (1<<next)) continue;
int ifUsed = commonLength[here][next]+play(next, visited+(1<<next));
if(play(here, visited) == ifUsed){
// cout << next << endl;
return piece[next].substr(commonLength[here][next])+reconstruct(next, visited+(1<<next));
}
}
return "**";

} 



[알고리즘 STEP. 3]

이제 다 좋은데, 3번째 테스트케이스처럼 어떤 문자열이 다른 문자열에 완전히 속하는 경우에는 아예 없는 샘 쳐줘야 한다. (어차피 흡수되서 없어질거니깐..)
그래서 다른 문자열에 완전히 속하는 문자열은 아예 삭제해주는 코드를 작성했다.

void cleansubstr(){
int erase = 0;
for(int i = 1; i<k; i++){
FOR(j, i){
if(piece[j].find(piece[i]) != string::npos){
piece.erase(piece.begin()+i);
i--; k--;
break;
}else if(piece[i].find(piece[j]) != string::npos){
piece.erase(piece.begin()+j);
i--;j--;k--;
}
}
}

} 



[최종코드]

//https://algospot.com/judge/problem/read/RESTORE

#include <iostream>
#include <vector>
#include <cstring>
#include <limits>
#define FOR(i, ed) for(int i = 0 ; i < ed; i++)
#define MINF std::numeric_limits<int>::min(); //*
using namespace std;

// input
int tc, k;
vector<string> piece;
//algorithm
int commonLength[16][16];
int cache[16][1<<16]; //j상태일 때 i에서부터 얻을수 있는 최대
void printcommonLength(){
FOR(i, k) {
FOR(j, k)
cout << commonLength[i][j] << " ";
cout << endl;
}
}
void print(vector<string> &a) {
for (string x : a) {
cout << x << ' ';
}
cout << '\n';
}
void cleansubstr(){
int erase = 0;
for(int i = 1; i<k; i++){
FOR(j, i){
if(piece[j].find(piece[i]) != string::npos){
piece.erase(piece.begin()+i);
i--;
k--;
break;
}else if(piece[i].find(piece[j]) != string::npos){
piece.erase(piece.begin()+j);
i--;
j--;
k--;
}
}
}
}

int calcCommon(int i , int j){
for(int common = min(piece[j].length(),piece[i].length()); common >0; common--){
FOR(index, common){
if(piece[i][piece[i].length()-common+index] != piece[j][index]) break;
if(index == common-1) return common;
}
}
return 0;

}
void precalc(){
//string i 뒤에 string j를 이어붙일 때 겹치는 길이
FOR(i, k){
FOR(j, k){
if(i==j) continue;
else commonLength[i][j] = calcCommon(i, j);
}
}
}

int play(int here, int visited){
if(visited == (1<<k)-1) return 0;
int &ret = cache[here][visited];
if(ret >0) return ret;
ret = MINF;
FOR(next, k){
if(visited & (1<<next)) continue;
ret = max(ret, commonLength[here][next]+play(next, visited+(1<<next))); // *
}
return ret;
}
string reconstruct(int here, int visited){
if(visited == (1<<k)-1) return "";
FOR(next, k){
if(visited & (1<<next)) continue;
int ifUsed = commonLength[here][next]+play(next, visited+(1<<next));
if(play(here, visited) == ifUsed){
// cout << next << endl;
return piece[next].substr(commonLength[here][next])+reconstruct(next, visited+(1<<next));
}
}
return "**";
}

int main(){
// freopen("input.txt", "r", stdin);
cin >> tc;
while(tc--){
piece.clear();
memset(commonLength, 0, sizeof(commonLength));
memset(cache, -1, sizeof(cache));
cin >> k;
FOR(i, k){
string temp;
cin >> temp;
piece.push_back(temp);
}
cleansubstr();
// print(piece);
precalc();
// printcommonLength();
int rslt = MINF;
int start;
FOR(i, k){
int temp = play(i, 1<<i);
if(rslt <= temp){
rslt = temp;
start = i;
}
}
// cout << play(start, 1<<start) << endl;
cout << piece[start]+reconstruct(start, 1<<start) << endl;
}
}


 



[내가 헤맨 부분]

- 외판원문제랑 거의 똑같아서 앞부분까지 잘 했는데, 다른 문자열에 포함되는 문자열을 삭제하는 부분에서 인덱스를 제대로 안빼줘서 계속 오답이 나왔다.

무슨 생각을 했었는지 

if(piece[j].find(piece[i]) != string::npos) 에서는 i--만 하고, 

else if(piece[i].find(piece[j]) != string::npos)에서는 j--만 해줬다.. 

아마 break문으로 반복문 빠져나가니깐 상관 없다고 생각했었나 보다.....

void cleansubstr(){
int erase = 0;
for(int i = 1; i<k; i++){
FOR(j, i){
if(piece[j].find(piece[i]) != string::npos){
piece.erase(piece.begin()+i);
i--; k--;
break;
}else if(piece[i].find(piece[j]) != string::npos){
piece.erase(piece.begin()+j);
i--;j--;k--;
}
}
}

} 



반응형