[백준] | C++ | 10942. 펠린드롬? (Top-down DP)

2025. 4. 20. 22:49·알고리즘/C++

개요

골드 1을 달성했습니다.

 

그런 의미에서 이번에 푼 문제는 클래스 5의 문제인 10942. 펠린드롬? 입니다.

무슨 상관이냐구요? 클래스 5를 달면 플레를 더 빨리 갈 수 있거든요.

 

 


 

 

문제

명우는 홍준이와 함께 팰린드롬 놀이를 해보려고 한다.

먼저, 홍준이는 자연수 N개를 칠판에 적는다. 그 다음, 명우에게 질문을 총 M번 한다.

각 질문은 두 정수 S와 E(1 ≤ S ≤ E ≤ N)로 나타낼 수 있으며, S번째 수부터 E번째 까지 수가 팰린드롬을 이루는지를 물어보며, 명우는 각 질문에 대해 팰린드롬이다 또는 아니다를 말해야 한다.

예를 들어, 홍준이가 칠판에 적은 수가 1, 2, 1, 3, 1, 2, 1라고 하자.

  • S = 1, E = 3인 경우 1, 2, 1은 팰린드롬이다.
  • S = 2, E = 5인 경우 2, 1, 3, 1은 팰린드롬이 아니다.
  • S = 3, E = 3인 경우 1은 팰린드롬이다.
  • S = 5, E = 7인 경우 1, 2, 1은 팰린드롬이다.

자연수 N개와 질문 M개가 모두 주어졌을 때, 명우의 대답을 구하는 프로그램을 작성하시오.

 

입력

첫째 줄에 수열의 크기 N (1 ≤ N ≤ 2,000)이 주어진다.

둘째 줄에는 홍준이가 칠판에 적은 수 N개가 순서대로 주어진다. 칠판에 적은 수는 100,000보다 작거나 같은 자연수이다.

셋째 줄에는 홍준이가 한 질문의 개수 M (1 ≤ M ≤ 1,000,000)이 주어진다.

넷째 줄부터 M개의 줄에는 홍준이가 명우에게 한 질문 S와 E가 한 줄에 하나씩 주어진다.

 

출력

첫째 줄에 수열의 크기 N (1 ≤ N ≤ 2,000)이 주어진다.

둘째 줄에는 홍준이가 칠판에 적은 수 N개가 순서대로 주어진다. 칠판에 적은 수는 100,000보다 작거나 같은 자연수이다.

셋째 줄에는 홍준이가 한 질문의 개수 M (1 ≤ M ≤ 1,000,000)이 주어진다.

넷째 줄부터 M개의 줄에는 홍준이가 명우에게 한 질문 S와 E가 한 줄에 하나씩 주어진다.

 

입력

총 M개의 줄에 걸쳐 홍준이의 질문에 대한 명우의 답을 입력으로 주어진 순서에 따라서 출력한다. 팰린드롬인 경우에는 1, 아닌 경우에는 0을 출력한다.

 

테스트 케이스

입력 출력
7
1 2 1 3 1 2 1
4
1 3
2 5
3 3
5 7
1
0
1
1

 

 


 

 

풀이

아이디어 정리

일단 알고리즘 분류는 깡 DP입니다.

이러면 점화식만 떠올릴 수 있다면 쉽습니다.

 

일단 펠린드롬이 무엇인지부터 생각해봅시다.

앞으로 읽을때와 뒤로 읽을 때가 동일한 수를 의미하는게 펠린드롬이죠.

 

 

이때 펠린드롬의 특성을 하나 알 수 있습니다.

앞 뒤로 수를 하나씩 잘라내더라도 그 수는 여전히 펠린드롬입니다. 이걸 이용해서 DP를 짜볼 수 있어요.

 

 

일단 입력은 S와 E가 들어오죠. S번째 수 부터 E번째 수가 펠린드롬인지의 여부를 0과 1로 출력 하면 돼요.

그러면 1차원 배열의 DP로는 택도 없습니다. 2차원 배열을 사용해보죠.

 

dp[i][j]에 수열의 i번째 수 부터 j번째 수 펠린드롬을 이루는지에 대한 여부를 담을겁니다.

그리고 위의 특성을 이용하면 dp[i][j]가 성립하기 위해서는 dp[i+1][j-1]이 성립함과 동시에 수열의 i번째 수와 j번째 수가 동일해야 한다는걸 알 수 있습니다.

 

그러니 수열을 seq라고 선언했을때 점화식은 다음과 같이 나옵니다.

dp[i][j] = dp[i+1][j-1] && seq[i] == seq[j]

 

 

코드로 풀기

아이디어는 전부 나왔죠? 그러면 코드는 쉽습니다.

일단 입력을 전부 받아줍시다.

 

dp는 모두 -1로 초기화 해둡니다

int n;
cin >> n;

vector<int> seqVec(n + 1);
vector<vector<int>> dp(n + 1, vector<int>(n + 1, -1));
for (int i = 1; i <= n; i++)
{
	cin >> seqVec[i];
}

int m;
cin >> m;
for (int i = 0, s, e; i < m; i++)
{
	cin >> s >> e;
}

 

 

저는 이번 문제 풀이에서 Bottom-up 대신 Top-down 이라는 기법을 사용해볼겁니다.

둘의 차이점은 속도인데 솔직히 거기서 거기에요. 시간 복잡도는 Bottom-up이 \(O(n^2)\)이고 Top-down이 \(O(n^2+m)\)으로 Top-down이 더 느려요. 근데 테스트케이스에 따라 Top-down이 더 빠르기도 합니다.

왜 이렇게 차이가 나냐면 Bottom-up은 dp배열을 미리 꽉꽉 채워넣는데 Top-down은 들어온 입력에 따라 실시간으로 dp배열을 채우기 때문이에요. Top-down은 함수 호출 등의 오버헤드 때문에 상수 시간 \(O(1)\)을 테스트 케이스의 개수인 M개만큼 더해서 더 느립니다.
구현도 Top-down이 조금 더 길어요.


아무튼 제가 Top-down을 이번에 시도해본 이유는 그냥 적다보니까 Top-down이 돼있었어요.

 

 

아무튼 이제 핵심 함수인 checkPelindrome 함수를 적어줍시다.

bool checkPelindrome(vector<int>& seq, vector<vector<int>>& dp, int s, int e)

 

이렇게 함수 적을때마다 드는 생각인데 그냥 전역 배열을 두는게 더 괜찮아보이는 것 같습니다.

 

아무튼 이 함수는 s와 e를 받아서 수열의 s번째 수 부터 e번째 수 까지가 펠린드롬인지의 여부를 반환합니다.

 

 

일단 dp[s][e]가 이미 계산 되어있다면 그걸 반환합니다. 이거 없으면 메모이제이션이 아니에요.

if (dp[s][e] != -1)
	return dp[s][e];

 

그리고 몇가지 분기를 체킹해야 합니다.

s와 e가 같은 경우 그 수는 무조건 펠린드롬입니다. 왜냐하면 그냥 수열에 수가 하나밖에 없어서 어디로 읽던간에 무조건 똑같거든요.

 

그리고 만약 s와 e의 차가 1, 그러니까 [1 2] 라던가 [2 3] 같이 수열의 길이가 2가 된다면 seq[s]와 seq[e]가 같은지만 체킹해주면 돼요.

이런식으로 대입과 동시에 반환해주면 됩니다.

if (s == e)
	return dp[s][e] = 1;
if (s == e - 1)
	return dp[s][e] = seq[s] == seq[e];

 

 

그리고 그 무엇도 아니라면 재귀를 돌리면 됩니다. s는 한칸 전진하고 e는 한칸 후진해서 말이죠. 그리고 s+1과 e-1이 펠린드롬이라고 해도 seq[s]와 seq[e]가 같지 않다면 펠린드롬이 아니기 때문에 and 연산을 해줍시다.

return dp[s][e] = checkPelindrome(seq, dp, s + 1, e - 1) && (seq[s] == seq[e]);

 

그러면 checkPelindrome 함수가 완성됩니다.

 

그러면 이제 출력만 해주면 돼요.

for (int i = 0, s, e; i < m; i++)
{
	cin >> s >> e;

	cout << checkPelindrome(seqVec, dp, s, e) << '\n';
}

 

 

 


 

 

마치는 말

Top-down DP를 외판원 순회에서 사용해보면서 이번에 그 반동으로 복습이 되었는데 생각보다 이것도 괜찮네요.

재귀라는게 처음 접할땐 그냥 공포의 대상인데 은근히 재밌습니다.

 

 


 

 

코드 전문

bool checkPelindrome(vector<int>& seq, vector<vector<int>>& dp, int s, int e)
{
	if (dp[s][e] != -1)
		return dp[s][e];
	if (s == e)
		return dp[s][e] = 1;
	if (s == e - 1)
		return dp[s][e] = seq[s] == seq[e];

	return dp[s][e] = checkPelindrome(seq, dp, s + 1, e - 1) && (seq[s] == seq[e]);
}

int main()
{
	FAST_PRINT;

	//dp[i][j] = 수열의 i번째 수 부터 j번째 수가 펠린드롬 수면 1 아니면 0
	//dp[i][j] = dp[i+1][j-1] && seq[i] == seq[j];

	int n;
	cin >> n;

	vector<int> seqVec(n + 1);
	vector<vector<int>> dp(n + 1, vector<int>(n + 1, -1));
	for (int i = 1; i <= n; i++)
	{
		cin >> seqVec[i];
	}

	int m;
	cin >> m;
	for (int i = 0, s, e; i < m; i++)
	{
		cin >> s >> e;

		cout << checkPelindrome(seqVec, dp, s, e) << '\n';
	}
}

'알고리즘 > C++' 카테고리의 다른 글

[백준] | C++ | 1086. 박성원 (Bit DP, 나머지 연산)  (1) 2025.05.06
[백준] | C++ | 1557. 제곱 ㄴㄴ (포함-배제의 원리)  (5) 2025.04.29
[백준] | C++ | 4991. 로봇 청소기 (TSP, 비트마스킹)  (1) 2025.04.19
[백준] | C++ | 7453. 합이 0인 네 정수  (2) 2025.04.11
[백준] | C++ | 17626. Four Squares  (0) 2025.04.05
'알고리즘/C++' 카테고리의 다른 글
  • [백준] | C++ | 1086. 박성원 (Bit DP, 나머지 연산)
  • [백준] | C++ | 1557. 제곱 ㄴㄴ (포함-배제의 원리)
  • [백준] | C++ | 4991. 로봇 청소기 (TSP, 비트마스킹)
  • [백준] | C++ | 7453. 합이 0인 네 정수
SundG0162
SundG0162
블로그 프로필은 머핀입니다.
  • SundG0162
    게임개발고수가될거야
    SundG0162
  • 전체
    오늘
    어제
    • 분류 전체보기 (77)
      • 주저리 (2)
        • 잡담 (1)
        • 장현우 (0)
        • 회고록 (1)
      • 개발 (50)
        • CITADEL : 성채 (1)
        • HEXABEAT (1)
        • FRACTiLE (6)
        • UNNAMED (9)
        • Default Defense (10)
        • T-Engine (1)
        • Project EW (0)
        • 졸업작품 (0)
        • Unity (3)
        • C# (4)
        • C++ (13)
        • WinAPI (1)
        • 그 외 (0)
      • 알고리즘 (13)
        • C# (1)
        • C++ (12)
      • 자료구조 (2)
        • C++ (1)
        • C# (0)
        • 공용 (1)
      • 기타 (10)
        • 아트 (6)
        • AI (2)
        • 수학 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    코딩트리조별과제
    코딩테스트
    티스토리챌린지
    LLM
    AI
    코딩
    생성형ai
    오블완
    코드트리
    C#
    유니티
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
SundG0162
[백준] | C++ | 10942. 펠린드롬? (Top-down DP)
상단으로

티스토리툴바