-
ALOHA 월반 4주차 - DP (Interval DP / LIS / LCS)ALOHA - 2022/ALOHA - 초급반 → 중급반 월반 (2022 여름방학) 2022. 8. 19. 18:06
1. [22971] 증가하는 부분 수열의 개수 (Silver II)
더보기의도한 태그: Longest Increasing Subsequence
로 끝나는 LIS의 개수를 정의하면, 점화식은 아래와 같이 적어볼 수 있습니다. 면서 인 에 대해,
const ll mod= 998244353; int arr[5020]; ll dp[5020]; void Main(){ int n; cin >> n; for (int i = 1; i <= n; i++){ cin >> arr[i]; } for (int i = 1; i <= n; i++){ dp[i] = 1; for (int j = 1; j < i; j++){ if (arr[j] >= arr[i]){ continue; } dp[i] += dp[j]; dp[i] %= mod; } } for (int i = 1; i <= n; i++){ cout << dp[i] << ' '; } }
2. [2565] 전깃줄 (Gold V)
더보기의도한 태그: Longest Increasing Subsequence
왼쪽에서
번에 연결된 전깃줄이 오른쪽에서는 번에 연결되어있다고 해봅시다.그럼, 두 전깃줄이 교차하지 않는다는 건
면 라는 의미가 됩니다.그럼 최대한 많은 전깃줄을 남기려면,
면서 인 를 최대한 길게 해야 하므로이 문제는 그냥 LIS를 구하는 문제가 됩니다.
const int N = 500; int arr[520], dp[520]; void Main(){ int n; cin >> n; for (int i = 1; i <= n; i++){ int a, b; cin >> a >> b; arr[a] = b; } int ans = 0; for (int i = 1; i <= N; i++){ if (arr[i] == 0){ continue; } dp[i] = 1; for (int j = 1; j < i; j++){ if (arr[j] == 0){ continue; } if (arr[j] < arr[i]){ dp[i] = max(dp[i], dp[j]+1); } } ans = max(ans, dp[i]); //cout << arr[i] << " | DP " << i << " = " << dp[i] << endl; } cout << n-ans; }
3. [25343] 최장 최장 증가 부분 수열 (Gold V)
더보기의도한 태그: Longest Increasing Subsequence
를 끝으로 하는 LIS의 최대 길이 를 정의해봅시다.그럼, 점화식은 아래 값들의 최댓값이 됩니다. (기본값 = 1)
에 대해 라면:
문제의 답은
가 됩니다.+ 시간복잡도는
이지만, 실제로는 에 가까워서 2500만 회의 연산으로 AC를 받을 수 있습니다.int arr[120][120], dp[120][120]; void Main(){ int n; cin >> n; for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ cin >> arr[i][j]; } } int ans = 0; for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ int mx = 0; for (int y = 1; y <= i; y++){ for (int x = 1; x <= j; x++){ if (arr[y][x] < arr[i][j]){ mx = max(mx, dp[y][x]); } } } dp[i][j] = mx+1; ans = max(ans, dp[i][j]); } } cout << ans; }
4. [9252] LCS 2 (Gold IV)
더보기의도한 태그: Longest Common Substring
의 첫 글자와 의 첫 글자로 만들 수 있는 LCS의 길이 를 정의해봅시다.그럼, 점화식은 아래와 같이 나오게 됩니다.
의 번째 글자를 무시하고 LCS를 구해보기: 의 번째 글자를 무시하고 LCS를 구해보기: 의 번째 글자와 의 번째 글자를 LCS에 매칭시켜보기:- 이 경우는
여야 합니다.
- 이 경우는
위 3가지 경우 중 가장 큰 값을 들고 오면 점화식이 완성됩니다.
초항은, 한 쪽 문자열이 비어있을 때
이고,문제의 답은
가 됩니다.그럼 실제 LCS는 어떻게 구할까요?
우선
를 준비한 뒤, 아래와 같이 역추적을 돌려주면 됩니다. : LCS의 맨 앞에 를 추가 후, 를 각각 1씩 감소- 그 외의 경우:
와 중 더 큰 값을 가지고 있는 상태로 가면 됩니다.
int dp[1020][1020]; void Main(){ string s1, s2; cin >> s1 >> s2; int l1 = s1.size(), l2 = s2.size(); s1 = " " + s1; s2 = " " + s2; for (int i1 = 1; i1 <= l1; i1++){ for (int i2 = 1; i2 <= l2; i2++){ dp[i1][i2] = max(dp[i1-1][i2], dp[i1][i2-1]); if (s1[i1] == s2[i2]){ dp[i1][i2] = max(dp[i1][i2], dp[i1-1][i2-1] + 1); } } } cout << dp[l1][l2] << endl; int p1 = l1, p2 = l2; stack<char> stk; while (p1 > 0 && p2 > 0){ if (s1[p1] == s2[p2] && dp[p1-1][p2-1]+1 == dp[p1][p2]){ stk.push(s1[p1]); p1 -= 1; p2 -= 1; } else{ if (dp[p1-1][p2] > dp[p1][p2-1]){ p1 -= 1; } else{ p2 -= 1; } } } while (!stk.empty()){ cout << stk.top(); stk.pop(); } }
5. [11066] 파일 합치기 (Gold III)
더보기의도한 태그: Interval DP
구간의 파일을 하나로 합치는데 드는 총 비용 을 정의해봅시다.그럼 점화식은 아래와 같이 계산해볼 수 있습니다.
를 합치고 를 합친 뒤, 이 두 파일을 합치기: 을 합치고 를 합친 뒤, 이 두 파일을 합치기:- ...
을 합치고 를 합친 뒤, 이 두 파일을 합치기:
물론 저걸 저대로 구현하라고 하면 하기 싫을테니, 이를 간단하게 바꿔봅시다.
우선, 위 점화식을 아래와 같이 나타내볼 수 있습니다.
그런데 수열에서
이므로, 으로 줄여써볼 수 있습니다.물론 구간의 합을 구하는 건 시간이 오래 걸리니, 실제로는 누적합으로 빠르게 구해주면 됩니다.
초항은 간단하게
이고, 문제의 답은 이 됩니다.const ll INF = 1e15; ll arr[520], prf[520]; ll dp[520][520]; inline ll sum(int st, int ed){ return prf[ed] - prf[st-1]; } void Main(){ int t; cin >> t; while (t--){ int n; cin >> n; for (int i = 1; i <= n; i++){ cin >> arr[i]; } for (int i = 1; i <= n; i++){ prf[i] = prf[i-1] + arr[i]; } for (int l = 2; l <= n; l++){ for (int st = 1; ; st++){ int ed = st+l-1; if (ed > n){ break; } dp[st][ed] = INF; ll val = sum(st, ed); for (int k = st; k < ed; k++){ dp[st][ed] = min(dp[st][ed], dp[st][k]+dp[k+1][ed] + val); } } } cout << dp[1][n] << endl; } }
6. [1958] LCS 3 (Gold III)
더보기의도한 태그: Longest Common Substring
의 첫 글자, 의 첫 글자, 의 첫 글자로 만들 수 있는 최대 LCS 를 정의해봅시다.그럼, 점화식은 아래와 같은 경우에 대한 값의 최대가 됩니다.
의 번째 글자를 무시: 의 번째 글자를 무시: 의 번째 글자를 무시: 라면, 저 글자를 원래 LCS에 추가:
초항은
이고, 답은 이 됩니다.int dp[120][120][120]; void Main(){ string s1, s2, s3; cin >> s1 >> s2 >> s3; int l1 = s1.size(), l2 = s2.size(), l3 = s3.size(); for (int i = 0; i <= l1; i++){ for (int j = 0; j <= l2; j++){ for (int k = 0; k <= l3; k++){ if (i*j*k == 0){ dp[i][j][k] = 0; } else{ dp[i][j][k] = max({ dp[i][j][k-1], dp[i][j-1][k], dp[i-1][j][k] }); if (s1[i-1] == s2[j-1] && s2[j-1] == s3[k-1]){ dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k-1]+1); } } } } } cout << dp[l1][l2][l3]; }
7. [24940] Split the GSHS (Gold III)
더보기의도한 태그: Interval DP
구간의 학생들을 합치면서 떨어뜨릴 수 있는 친밀도 를 정의해봅시다.그럼 점화식은
인 에 대해 아래 값의 최대가 됩니다. 를 합친 뒤, 를 합치고, 이 둘을 합치기:- 여기서 함수
는 두 학생 무리를 합칠 때 감소시킬 수 있는 친밀도입니다.
- 여기서 함수
초항은
이고, 문제의 답은 이 됩니다.ll arr[90], prf[90]; ll dp[90][90]; int sgn(ll x){ return (x > 0) - (x < 0); } ll sum(int st, int ed){ return prf[ed] - prf[st-1]; } void Main(){ int n; cin >> n; for (int i = 1; i <= n; i++){ cin >> arr[i]; } for (int i = 1; i <= n; i++){ prf[i] = prf[i-1] + arr[i]; } for (int len = 2; len <= n; len++){ for (int st = 1, ed = len; ed <= n; st++, ed++){ for (int k = st; k < ed; k++){ ll res = dp[st][k] + dp[k+1][ed]; if (sgn(sum(st, k)) != sgn(sum(k+1, ed))){ res -= sum(st, k) * sum(k+1, ed); } dp[st][ed] = max(dp[st][ed], res); } //cout << "DP " << st << ' ' << ed << " = " << dp[st][ed] << endl; } } cout << -dp[1][n]; }
8. [7476] 최대 공통 증가 수열 (Gold I)
더보기의도한 태그: Longest Common Substring, Longest Increasing Subsequence
우선
가 증가하는 순서대로 스위핑을 날리면서, 전역으로 아래 DP를 업데이트해봅시다. 를 끝으로 하는 LCIS의 최대 길이 (초기값 = 0)그럼, 아래와 같이 DP를 업데이트해볼 수 있습니다.
라면, 이고 인 에 대해 를 로 업데이트
역추적은
를 끝으로 하는 길이 의 LCIS에서, 직전에 나오는 원소(의 인덱스) 를 저장해주면 됩니다.길이를 같이 저장해줘야 하는데, LIS와는 달리 LCIS는 앞의 원소로 끝내는 LCIS를 더 길게 만들 수 있기 때문입니다.
그런데 그걸 신경 안 썼다가는 역추적 배열이 꼬여버리겠죠.
ll arr[520], brr[520]; int dp[520], pre[520][520]; void Main(){ int n; cin >> n; for (int i = 1; i <= n; i++){ cin >> arr[i]; } int m; cin >> m; for (int i = 1; i <= m; i++){ cin >> brr[i]; } memset(dp, -1, sizeof(dp)); dp[0] = 0; for (int i = 1; i <= n; i++){ for (int j = 1; j <= m; j++){ if (arr[i] != brr[j]){ continue; } for (int k = 0; k < j; k++){ if (k==0 || brr[k] < brr[j]){ if (dp[j] < dp[k]+1){ dp[j] = dp[k]+1; pre[j][ dp[j] ] = k; //if (j == 4){ cout << "J4 " << dp[j] << ' ' << pre[j] << endl; } } //cout << "dp " << k << " = " << dp[k] << endl; } } //cout << "DP " << j << " = " << dp[j] << endl << flush; //system("pause"); } } int ans = 0, ptr = 0; //for (int i = 1; i <= n; i++){ cout << arr[i] << ' '; } cout << endl; for (int i = 1; i <= m; i++){ if (dp[i] > ans){ ans = dp[i]; ptr = i; } //cout << "DP " << i << ' ' << brr[i] << ' ' << dp[i] << ' ' << pre[i] << endl; } cout << ans << endl; stack<ll> stk; while (ans--){ stk.push(brr[ptr]); ptr = pre[ptr][ans+1]; } while (!stk.empty()){ cout << stk.top() << ' '; stk.pop(); } }
9. [1509] 팰린드롬 분할 (Gold I)
더보기의도한 태그: Interval DP
우선 팰린드롬 판별을 위해 아래 DP를 만들어봅시다.
가 회문인지 여부그럼 점화식은 아래 방식으로 만들어볼 수 있습니다.
가 회문이려면, 면서 여야 한다.
초항은
이 됩니다.근데 이렇게 회문 판별을 해도, 문제는 어떻게 풀어야 할까요?
를 분할하기 위해 필요한 팰린드롬의 최소 개수라는 DP를 하나 더 정의해보면, 아래와 같은 점화식을 생각해볼 수 있습니다.
인 에 대해, 의 최솟값
초항은
이고, 문제의 답은 이 됩니다.bool pal[2520][2520]; int dp[2520]; void Main(){ string s; cin >> s; int n = s.size(); s = " " + s; for (int i = 1; i <= n; i++){ pal[i][i] = 1; } for (int i = 1; i < n; i++){ pal[i][i+1] = (s[i] == s[i+1]); } for (int l = 3; l <= n; l++){ for (int i = 1; i <= n; i++){ int j = i + l-1; if (j > n){ break; } pal[i][j] = (s[i] == s[j] && pal[i+1][j-1]); } } for (int i = 1; i <= n; i++){ int mn = 1e9; for (int j = 0; j < i; j++){ if (pal[j+1][i]){ mn = min(mn, dp[j]); } } dp[i] = mn+1; } cout << dp[n]; }
'ALOHA - 2022 > ALOHA - 초급반 → 중급반 월반 (2022 여름방학)' 카테고리의 다른 글
ALOHA 월반 5주차 - Union Find / Minimum Spanning Tree (0) 2022.08.30 ALOHA 월반 3주차 - 최단 경로 (Dijkstra / Bellman-Ford / Floyd-Warshall) (0) 2022.08.09 ALOHA 월반 2주차 - 그래프 (DFS / BFS / Backtracking) 풀이 (0) 2022.08.01 ALOHA 월반 1주차 - 완전탐색 / 그리디 / 분할정복 (0) 2022.07.27