2017년 3월 31일 금요일

1588 수열

백준 1588 수열 https://www.acmicpc.net/problem/1588

문제는 간단하다. 매초마다 1은 132로 2는 211로 3은 232로 바뀐다. N초후에 왼쪽L번째부터 R번째까지 1,2,3숫자의 개수를 구하는 문제이다.

이 문제는  보고 어떻게 풀지 생각은 났는데 구현이 잘 안됬던 문제이다. 막연하게 연산시간을 생각하지 않고 짜면 금방 풀 수 있지만 당연히 TLE가 나올것이다. 그래서 시간을 줄이는 방법 두가지를 사용하였다.




<1>. L ~ R 이외의 범위 탐색X
1부터 시작한다면 위의 그림처럼 $N$초뒤에는 ${3}^{N}$의 숫자가 존재할 것이다. 이것은 어떤 시작점(1 또는 2또는 3)으로부터 $N$초후에 생기게 될 범위를 알 수 있다는 말이다.

예를들어 1에서 시작하여 2초뒤에 범위는 0~8(배열 0시작 기준)이다. 또 1초때 2번 index에서 시작하여 1초후의 범위는 6~8이다.
범위를 알게되면 굳이 탐색할 필요도 없는 공간에 대해 시간투자를 안해도 된다. 


<2>. L ~ R 내부 범위 탐색X
사실 $N$초후의 시작부터 마지막까지 1, 2, 3의 개수는 for문 N번만 돌리면 알 수 있다.
$a,b,c$를 각각 1,2,3의 개수라고 한다면 다음과 같이 나타낼 수 있다.
$a = a+2b$
$b = a+b+2c$  
$c = a+c$

따라서 시작점으로부터 N초후의 범위가 완전히 L ~ R사이에 존재한다면 이것역시 탐색을 할 필요가 없게된다.
예를들어 $시작=1, L=3, R=6, N =2$이라고 하면 
0초때는 0~8까지의 범위를 가질 것이다. 1초뒤에는 1, 3, 2의 숫자가 각각 0~2, 3~5, 6~8의 범위를 가지는데 온전한 범위 이내에 있는 3~5, 6~8은 탐색할 필요가 없다는것이다.

사실상 위의 예는 1초때 0~2의 범위도 범위 밖이므로 탐색할 필요가 없기때문에 1초까지만 탐색을 하면된다. (<1>의 경우)


#include <cstdio>
int X, L, R, N;
long long an1 = 0, an2 = 0, an3 = 0;
long long dp[4][21][4];
long long pow(int x, int y){
    if (y == 0)
        return 1;
    else if (y == 1)
        return x;
    if (y & 1)
        return pow(x, y - 1)*pow(x, 1);
    else
        return pow(x, y / 2)*pow(x, y / 2);
}
int sqrt(long long x){
    int cnt = 0;
    for (x; x != 1; x /= 3, cnt++);
    return cnt;
}
void cul(){
    long long a = 1, b = 0, c = 0;
    long long na, nb, nc;
    for (int n = 1; n <= 20; n++){
        na = a + 2 * b;
        nb = a + b + 2 * c;
        nc = a + c;
        a = na; b = nb; c = nc;
        dp[1][n][1= a; dp[1][n][2= b; dp[1][n][3= c;
    }
    a = 0; b = 1; c = 0;
    for (int n = 1; n <= 20; n++){
        na = a + 2 * b;
        nb = a + b + 2 * c;
        nc = a + c;
        a = na; b = nb; c = nc;
        dp[2][n][1= a; dp[2][n][2= b; dp[2][n][3= c;
    }
    a = 0; b = 0; c = 1;
    for (int n = 1; n <= 20; n++){
        na = a + 2 * b;
        nb = a + b + 2 * c;
        nc = a + c;
        a = na; b = nb; c = nc;
        dp[3][n][1= a; dp[3][n][2= b; dp[3][n][3= c;
    }
}
void loop(int num, long long range, long long first, long long second){
    if ((range != 1&& (R < first || (L > second)))
        return;
    if ((range != 1&& (L <= first && second <= R)){
        int r = sqrt(range);
        an1 += dp[num][r][1]; an2 += dp[num][r][2]; an3 += dp[num][r][3];
        return;
    }
    if (range == 1){
        if (L <= first && R >= first){
            if (num == 1)
                an1++;
            else if (num == 2)
                an2++;
            else
                an3++;
            return;
        }
        else
            return;
    }
    long long new_range = range / 3;
    long long f1 = first, s1 = f1 + new_range - 1;
    long long f2 = s1 + 1, s2 = f2 + new_range - 1;
    long long f3 = s2 + 1, s3 = f3 + new_range - 1;
    if (num == 1){
            loop(1, new_range, f1, s1);
            loop(3, new_range, f2, s2);
            loop(2, new_range, f3, s3);
    }
    else if (num == 2){
            loop(2, new_range, f1, s1);
            loop(1, new_range, f2, s2);
            loop(1, new_range, f3, s3);
    }
    else if (num == 3){
            loop(2, new_range, f1, s1);
            loop(3, new_range, f2, s2);
            loop(2, new_range, f3, s3);
    }
    return;
}
int main(){
    scanf("%d%d%d%d"&X, &L, &R, &N);
    cul();
    loop(X, pow(3, N), 0, pow(3, N) - 1);
    printf("%d %d %d\n", an1, an2, an3);
    return 0;
}
cs

2017년 3월 26일 일요일

1068 트리

백준 1068 트리 https://www.acmicpc.net/problem/1068

트리가 존재할때 어떤 노드하나를 지웠을 경우 남은 트리에서 리프(leaf)노드의 개수를 구하는 문제이다.

인접행렬을 만들어 연결된 점들을 저장한후에 dfs탐색으로 제거될 노드를 제외하고 탐색을 시키며 리프노드의 개수를 구한다.


#include <cstdio>
int arr[51][51];
int N, M, root;
int ans = 0;
void dfs(int num){
    int cnt = 0;
    if (num == M)                //지워야하는 노드에 들어갔을 경우 반환
        return;
    for (int n = 0; n < N; n++){
        if (num != n && arr[num][n] == 1 && n != M){        // num -> n으로 연결된 곳으로 dfs
            cnt++;
            dfs(n);
        }
    }
    if (cnt == 0)        //leaf노드일 경우
        ans++;
    return;
}
int main(){
    scanf("%d"&N);
    for (int n = 0; n < N; n++){
        int data;
        scanf("%d"&data);
        if (data == -1)
            root = n;
cs

2017년 3월 24일 금요일

2616 소형기관차

백준 2616 소형기관차 https://www.acmicpc.net/problem/2616

소형기관차 3대를 이용하여 최대로 운송할 수 있는 손님 수를 구하는 문제이다.

$DP[N][M]$ : 소형기관차를 N대 운행할 때 M번째 객차를 선택했을 경우의 최대 운송 손님 수(기관차에 K개의 객실을 넣을 때)

배열 $S[N]$을 이용해 N번째 객차까지의 손님 수를 모두 더
해 놓는다.

$S[N] = S[N-1]+arr[N]$

이렇게 해놓으면 나중에 N번째 객차에 해당하는 기관차의 손님수를 바로 알 수 있다.
$S[N]-S[N-K]$


$DP[N][M] = max(DP[N][M-1], DP[N - 1][M - K] + S[M]-S[M-K]$


                                                                                   <문제 예시의 DP값>
따로 점화식에 대한 풀이는 하지 않겠다.

#include <cstdio>
#include <algorithm>
using namespace std;
int arr[50001];    
int S[50001];
int dp[4][50001];
int main(){
    int N, K;
    int ans = 0;
    scanf("%d"&N);
    for (int n = 1; n <= N; n++){
        scanf("%d"&arr[n]);
        S[n] = S[n - 1+ arr[n];
 
    }
    scanf("%d"&K);
    for (int n = 1; n <= 3; n++){
        for (int m = n*K; m <= N; m++){
            if (n == 1)
                dp[n][m] = max(dp[n][m-1], S[m] - S[m - K]);
            else
                dp[n][m] = max(dp[n][m-1],dp[n - 1][m - K] + S[m]-S[m-K]);
            
            ans = max(ans, dp[n][m]);
        }
    }
    printf("%d\n", ans);
    return 0;
}
cs

2017년 3월 19일 일요일

2011 암호코드

백준 2011 암호코드 https://www.acmicpc.net/problem/2011

A를 1. B를 2, .... Z를 26으로 암호화 할 때 수 5000자리 이하의 수 N에 대한 암호의 경우의 수를 구하는 문제이다.

처음 문제를 접했을 때는 고민하다가 결국 못풀었는데 다시 푸니 풀 수 있게됬다.
먼저 DP를 다음과 같이 정의하였다. 

$DP[N][0]$: N번째 숫자를 일의자리 숫자로 취급했을 때의 경우의 수
$DP[N][1]$: N번째 숫자를 십의자리 숫자로 취급했을 때의 경우의 수

즉 213이라는 숫자를 예로들면 
DP[3][0] : {2,1,3}, {21,3} - 2가지
DP[3][1] : {2,13} - 1가지 



먼저 $DP[N][0]$인 경우를 살펴보면 0을 제외한 숫자에 대해서는 앞의경우가 어떤경우에도 상관없음을 알 수 있다.
위의 경우에서는 {12, 3} 또는 {1, 2, 3}


0일때의  경우는 어떨까?
이때는 단독으로 0혼자만 올 수 있는 암호가 존재하지 않는다. 따라서 이 경우에 대해서는 10이나 20인 경우밖에 없기때문에 0이된다.

$DP[N][1]$인 경우에 대해 생각해 보자. 먼저 마지막 숫자를 십의 자리로 만드는 경우는 10~26밖에 없다. 이 경우는 N-1번째를 일의 자리로 올 수 있는 경우로만 해야한다.
{1, 23}

십의 자리가 안되는 경우는 당연히 그 경우가 없기에 0이다.

이걸 정리하면 점화식은 다음과같이 된다.

$if(S[N] == 0)$
         $DP[N][0] = 0$
$else$
         $DP[N][0] = DP[N-1][0]+DP[N-1][1]$

$if(10 <= S[N-1]S[N] <= 26)$
         $DP[N][1] = DP[N-1][0]$
$else$
         $DP[N][1] = 0$

#include <iostream>
#define MOD 1000000
using namespace std;
int dp[5001][2];
int main(){
    std::ios::sync_with_stdio(false);
 
    char str[5001];
    int arr[5001];
    int len;
    
    scanf("%s", str);
    for (len = 0; str[len] != NULL; len++)
        arr[len + 1= str[len] - '0';
 
    dp[1][0= arr[1== 0 ? 0 : 1;
    for (int n = 2; n <= len; n++){
        if (arr[n] == 0)
            dp[n][0= 0;
        else
            dp[n][0= dp[n - 1][0] % MOD + dp[n - 1][1] % MOD;
 
        if ((arr[n - 1== 1 && (arr[n] >= 0 && arr[n] <= 9)) || (arr[n - 1== 2 && (arr[n] >= 0 && arr[n] <= 6)))
            dp[n][1= dp[n - 1][0] % MOD;
        else
            dp[n][1= 0;
    }
    printf("%d\n", (dp[len][0] % MOD + dp[len][1] % MOD) % MOD);
    return 0;
}
cs

2017년 3월 18일 토요일

2688 줄어들지 않아

백준 2688 줄어들지 않아 https://www.acmicpc.net/problem/2688

0001, 0004, 1234 들은 줄어들지 않는 숫자들이다.
자릿수 N이 주어졌을 때 줄어들지 않는 숫자들의 갯수를 구하는 문제이다.

$D[N][K]$ : 마지막 숫자가 K인 자릿수 N개의 줄어들지 않는 숫자들의 갯수

D[3][2]를 예로 들어보자.
경우는 {0,0,2}, {0,1,2}, {1,1,2}, {0,2,2}, {1,2,2}, {2,2,2}가 있겠다.
마지막수 2를 제외하고 앞의 두자리만 보면 {0,0}, {0,1}, {1,1}, {0,2}, {1,2}, {2,2}
이숫자들은 D[2][0], D[2][1], D[2][2]임을 알 수 있다.

즉, D[3][2] = D[2][0] + D[2][1] + D[2][2]
                = D[3][1] + D[2][2]

$D[N][K] = \sum_{0}^{K-1}D[N][K]+D[N-1][K]$

∴ $D[N][K] = D[N][K-1]+D[N-1][K]$
#include <cstdio>
#include <cmath>
long long dp[65][10];
int main(){
    int T;
    scanf("%d"&T);
    for (int n = 0; n <= 9; n++){
        dp[1][n] = 1;
        dp[2][n] = n + 1;
    }
    while (T--){
        int N;
        scanf("%d"&N);
        if (dp[N][0== 0){
            for (int n = 3; n <= N; n++){
                dp[n][0= 1;
                for (int k = 1; k <= 9; k++)
                    dp[n][k] = dp[n - 1][k] + dp[n][k - 1];
            }
        }
        long long ans = 0;
        for (int n = 0; n <= 9; n++)
            ans += dp[N][n];
        printf("%lld\n", ans);
    }
    return 0;
}
cs

2159 케익 배달

백준 2159 케익 배달 https://www.acmicpc.net/problem/2159

아무리 생각해도 2중 for문을 안쓰고 문제를 해결할 수가 없다고 생각했는데 내 생각이 맞았던 문제다.


N번째 사람이 4번위치에 있을 때 케익을 0~4번위치에서 줄 수 있다.



따라서 N-1번째 사람에게 케익을 주고 N번째 사람에게 케익을 줄때 최단거리는 위와같이 위치 하나당 5가지의 경우 총 25가지 경우를 비교해서 각 위치의 최단거리를 저장하면된다.

#include <cstdio>
#include <algorithm>
#define MAX 987654321987
using namespace std;
unsigned long long dp[2][5];
int main(){
    int N, X, Y;
    int dx[5= {-1,0,0,1,0}, dy[5= {0,-1,1,0,0};
    scanf("%d"&N);
    scanf("%d %d"&X, &Y);
    for (int n = 0; n < 2; n++)
        for (int m = 0; m < 5; m++)
            dp[n][m] = MAX;
    for (int n = 1; n <= N; n++){
        int cx, cy;
        scanf("%d %d"&cx, &cy);
        for (int i = 0; i < 5; i++){
            if (cx + dx[i] >= 0 && cx + dx[i] <= 100000 && cy + dy[i] >= 0 && cy + dy[i] <= 100000){
                if (n==1)
                    dp[n%2][i] = abs(cx + dx[i] - X) + abs(cy + dy[i] - Y);        //빵집->첫번째 사람
                else{
                    dp[n % 2][i] = MAX;
                    for (int j = 0; j < 5; j++)
                        dp[n%2][i] = min(dp[n%2][i],abs(cx + dx[i] - (X + dx[j])) + abs(cy + dy[i] - (Y + dy[j])) + dp[(n+1)%2][j]);
                        //n-1번째 사람->n번째 사람
                }
            }
        }
        X = cx; Y = cy;
    }
    unsigned long long ans = MAX;
    for (int n = 0; n < 5; n++)
        ans = min(ans, dp[N % 2][n]);
    printf("%lld\n", ans);
    return 0;
}
cs