[문제 링크]


DP문제이다. 경로 모든 경로의 수를 구하는 미로 찾기와 같은 문제랑 다를게 없는데, 거기서 내리막으로만 갈 수 있다는 조건만 주면 되는 문제이다.

맵의 끝에 도달하면 1을 반환하고, 아닐 경우 0을 반환하여 경로의 갯수를 구한다.

밑은 이를 구현한 코드이다.


#include <cstdio>
#include <cstring>
int m,n,arr[501][501];
int relPos[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
int cache[501][501]; 
int dp(int y,int x){
    //기저사례 : 끝에 도달했으면 탈출 
    if(y==m && x==n) return 1;  
    int& ret = cache[y][x];
    if(ret!=-1) return ret;
    ret = 0;
    for(int c = 0; c<4; c++){
        int px = x + relPos[c][0];
        int py = y + relPos[c][1];
        //맵을 안넘어서고 내리막이면 
        if(px <=n && py<=m && px>=1 && py>=1&&arr[py][px] < arr[y][x])
            ret +=dp(py,px);
    }
    return ret;
} 
int main(){
    memset(cache,-1,sizeof(cache));
    scanf("%d %d",&m,&n);
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&arr[i][j]);
        }
    }
    printf("%d",dp(1,1));
} 


경로 문제를 자주 보다보니 경로 문제는 이제 다 비슷비슷하다는 생각이 든다.

'Algorithm > Problems' 카테고리의 다른 글

백준 - 2493 탑  (1) 2016.03.28
백준 - 10799 쇠막대기  (2) 2016.03.28
백준 - 9465 스티커  (0) 2016.03.15
백준 - 1654 랜선자르기  (0) 2016.03.15
백준 - 1912 연속합  (0) 2016.03.07


[문제 링크]




DP문제이다. 어려운 문제는 아니고, 다음으로 선택할 수 있는 경로의 경우만 알면 쉽게 풀리는 문제인 것 같다.

자신을 기준으로 상하좌우는 접근할 수 없기 때문에, 대각선에 위치한 스티커나, 대각선에서 오른쪽으로 한칸 옆에 있는 스티커를 다음 경로로 선택하면 된다.

이렇게 모든 점수들을 더한 값 중 최대값을 구하면 되었다.

밑은 이를 구현한 코드이다.



#include <cstdio> #include <cstring> #include <algorithm> int t,n,arr[2][100000],cache[2][100000]; int relPos[4][2] = {{1,1},{1,-1},{2,-1},{2,1}}; int dp(int y, int x){ //기저사례 : y 끝에 도달했을 때 if(x==n-1) return arr[y][x]; int& ret = cache[y][x]; if(ret!=-1) return ret; ret = 0; for(int i=0;i<4;i++){ int px = x + relPos[i][0]; int py = y + relPos[i][1]; //스티커 범위를 초과하지 않으면 if(px<n&&px>=0&&py<2&&py>=0){ //최대값 구하기, 기존 ret와, 이 값+ 현재 좌표값 ret = std::max(ret,dp(py,px)+arr[y][x]); } } return ret; } int main(){ scanf("%d",&t); while(t--){ memset(cache,-1,sizeof(cache)); scanf("%d",&n); for(int y=0;y<2;y++){ for(int x=0;x<n;x++){ scanf("%d",&arr[y][x]); } } //(0,0)으로 시작할 때랑, (1,0)으로 시작할때 중 최댓값을 구하여 출력한다. printf("%d\n",std::max(dp(0,0),dp(1,0))); } }


'Algorithm > Problems' 카테고리의 다른 글

백준 - 10799 쇠막대기  (2) 2016.03.28
백준 - 1520 내리막 길  (0) 2016.03.15
백준 - 1654 랜선자르기  (0) 2016.03.15
백준 - 1912 연속합  (0) 2016.03.07
백준 - 2294 동전 2  (0) 2016.03.07




이전에 풀었던 문제인데, 재채점으로 인해 틀렸다하여 다시 풀었던 문제이다.(자료형만 바꿔주니 다시 맞게 나왔다.)

이분법으로 푸는 문제인데 이분 탐색에서 upper bound 방식으로 풀어야 한다.

왜냐하면 필요한 랜선의 길이인 N을 만드는 경우는 엄청 다양하기 때문이다. 그 다양한 길이 중 가장 긴 랜선의 길이를 찾아야한다.


밑은 이 문제의 코드이다.



#include <cstdio>
#include <algorithm>
long long k,n,l[10010], mi=0,m; 
long long bs(long long s, long long e) {
    //이분 탐색을 시작한다.
    while (s<=e) {
        m = (s + e) / 2;
        long long v = 0;
        // 각 종류의 랜선들을 현재의 길로 나눠 갯수를 구한다. 
        for (int i = 0; i < k; i++)
            v += l[i] / m;
            
        //갯수가 '같거나' 크면 m의 값을 1 증가시켜주고 이분탐색을 진행한다. 
        if (v >= n) s = m + 1;
        else if (v < n) e = m - 1;
    }
    return e;   
}
int main() {
    scanf("%lld %lld", &k, &n);
    for (int i = 0; i<k; i++) {
        scanf("%lld", &l[i]);
        mi = std::max(mi, l[i]);
    }
    printf("%lld",(n==1)?mi:bs(1, mi));
}

'Algorithm > Problems' 카테고리의 다른 글

백준 - 1520 내리막 길  (0) 2016.03.15
백준 - 9465 스티커  (0) 2016.03.15
백준 - 1912 연속합  (0) 2016.03.07
백준 - 2294 동전 2  (0) 2016.03.07
백준 - 2293 동전 1  (1) 2016.03.07


[문제 링크]


최대 연속합을 구하는 문제이다. 숫자 배열과, 최대 연속합을 저장하는 배열이 따로 더 필요하다.


구하고자 하는 숫자 배열의 가장 끝부터 차례로 내려가며, 최대연속합을 찾고자하는 배열값과, 바로 뒤 배열을 기점으로 하는 최대 연속합과의 합을 비교하여 그 최대값이 최대 연속 합이다.


밑은 예제를 돌렸을 경우의 CACHE 배열의 값이다. 맨 마지막의 CACHE값은 자기 뒤의 값이 없으므로 -1이 된다. 그 다음부터 재귀를 탈출해나가며 최대연속합을 구해나간다.


 ARR

 10

 -4

 3

 1

 5

 6

 -35

 12

 21

 -1

 CACHE

 21

 11

 15

 12

 11

 6

 -2

 33

 21

 -1


예를 들면 ARR이 21인 값은, 자신의 값 21과, 바로 뒤를 기점으로하는 최대 연속합과의 합인 21-1=20을 비교해서 큰 값이 그 위치를 기점으로하는 최대 연속합이다.

21인 경우에는 12와, 12+21=33 을 비교해서 큰 값이 그 위치를 기점으로하는 최대 연속합이다. 


즉 이 문제의 점화식은 다음과 같다.


DP(i) = MAX(arr[i], arr[i] + DP(i+1))



위 로직에 전체의 최대연속합을 저장하는 변수를 따로 만들어 답을 구하였다. 밑은 이를 구현한 코드이다.


#include <cstdio> #include <algorithm> int n,arr[100001],cache[100001]; int dp(int num){ //기저 사례 : 마지막에 도달했을 때, 마지막 값을 시작으로 하는 최대 연속합은 자기 자신이므로. if(num==n){ cache[n] = arr[n]; return arr[n]; } int& ret = cache[num]; //뒤의 값을 기준으로 하는 최대 연속값을 구하는 재귀 실행. int mx = dp(num+1); //점화식 : 자기 자신과, 바로 뒤의 값을 시작으로하는 최대 연속합과의 합중 최대값을 구한다. ret = std::max(arr[num],arr[num]+cache[num+1]); //지금까지 구한 연속합중 최대값을 구한다. mx = std::max(mx,ret); return mx; } int main(){ //초기화 scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&arr[i]); //구한 최대 연속합중 최대값을 찾는다. printf("%d",dp(1)); }


막상 풀고 보니 재귀로 푸는 것 보다 반복문이 훨씬 깔끔하게 나올 것 같았다. 밑은 재귀를 쓰지 않고 for문으로만 구한 코드이다.


#include <cstdio>
#include <algorithm>
int n,arr[100001],cache[100001];
int dp(){
    //가장 마지막 값은 그 자체가 최대연속합이므로 배열값을 넣어준다. 
    cache[n] = arr[n]; 
    int mx = arr[n];
    for(int i = n-1;i>0;i--){
        //점화식 : 자기 자신과, 바로 뒤의 값을 시작으로하는  최대 연속값과의 합중 최대값을 구한다.
        cache[i] = std::max(arr[i],arr[i]+cache[i+1]);
        //지금까지 구한 연속합중 최대값을 구한다.
        mx = std::max(mx,cache[i]);
    } 
    return mx;
}
int main(){
    //초기화 
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&arr[i]);
    //구한 최대 연속합중 최대값을 찾는다. 
    printf("%d",dp());
}


'Algorithm > Problems' 카테고리의 다른 글

백준 - 9465 스티커  (0) 2016.03.15
백준 - 1654 랜선자르기  (0) 2016.03.15
백준 - 2294 동전 2  (0) 2016.03.07
백준 - 2293 동전 1  (1) 2016.03.07
백준 - 1697 숨바꼭질  (1) 2016.02.28


[문제 링크]


동전1에서 이어지는 문제다. 동전1은 경우의 갯수를 구하는 것이라면, 동전2 문제는 해당 경우에 사용되는 동전의 갯수를 구하는 것이다.


예를 들어 4원까지 동전 1,2만을 이용해서 갯수를 구하는 경우를 그려서 점화식을 구해보면



 

 1 동전 갯수

 2 동전 갯수

 DP(V)

 1일 때 DP(1)

 1 = DP(1-1)+1

 

 1

 2일 때 DP(2)

 2 = DP(2-1)+1

1 = DP(2-2)+1

 1

 3일 때 DP(3)

 3 = DP(3-1)+1

2 = DP(3-2)+1

 2

 4일 때 DP(4)

 4 = DP(4-1)+1

2 = DP(4-2)+1

 2


즉 DP(V) = min(DP(V), DV(V-COIN(i))+1) 의 점화식을 구할 수 있다.


다만 비교의 대상을 지정해야 하므로, 아무 값도 입력이 되어있지 않았을 때엔 최소 최적값 비교를 할 수 없기 때문에 DP(N) = DP(N- COIN(i)) + 1로 초기화 해주어야 한다.(초기화를 안해주면 DP(0)=0이므로 출력값은 0만 나온다..)


지금 동전 갯수를 구할 값이 초기화 된 값인지 아닌지, 이를 구하는데 필요한 이전 값이 초기화 되어있는지 조건을 걸어줘서 문제를 풀면 된다.


밑은 이를 구현한 코드이다.


#include <cstdio> #include <cstring> #include <algoritm> using namespace std; int n,coin[101],k,cache[10001]={0,}; int main(){ memset(cache,-1,sizeof(cache)); scanf("%d %d",&n,&k); for(int i=0;i<n;i++){ scanf("%d",&coin[i]); } cache[0] = 0; //각 동전별로 해당 값을 구하는데 드는 최소 횟수를 구한다. for(int i = 0; i<n; i++){ for(int j = 0; j<=k;j++){ if(j>=coin[i] && cache[j-coin[i]]!=-1){ //아무 값도 입력이 안됬을 경우엔 동전의 갯수를 초기화해준다. if(cache[j]==-1) cache[j] = cache[j-coin[i]]+1; //점화식, 값이 입력되어있으면 이전값과 비교해서 최소값을 넣어준다. else cache[j] = min(cache[j],cache[j-coin[i]]+1); } } } printf("%d",cache[k]); }


'Algorithm > Problems' 카테고리의 다른 글

백준 - 1654 랜선자르기  (0) 2016.03.15
백준 - 1912 연속합  (0) 2016.03.07
백준 - 2293 동전 1  (1) 2016.03.07
백준 - 1697 숨바꼭질  (1) 2016.02.28
백준 - 1005 ACM Craft  (0) 2016.02.27

[문제 링크]


DP문제이다. 정말 내 머리가 DP에서 틀에 박혔다는 생각이 든 문제였다. 원래는 DP[V] = DP[V] + DP[V-COIN[I]]와 같은 점화식으로 문제를 풀어나갔는데.


이 식의 문제점은 다른 순서지만, 같은 종류의 동전을 사용한 것도 모두 다른 값으로 취급해 카운트를 하는 것 같았다. 그래서 그냥 단순하게 위와 같은 방식으로 문제를 풀 경우 출력값이 엄청 크게 나왔다..


그래서 내가 원래 풀었던 것과 반복을 다른 방법으로 진행해야 하는데, 그것은 각 값 별로 방법의 수를 구하는 것이 아니라, 동전 별로 각 값에 대한 방법의 수를 구하는 것이다.



밑의 코드는 원래 방식대로, 값을 기준으로 재귀를 진행한 코드이다. 1,1,1,2와 2,1,1,1과 같은 같은 종류지만 다른 순서로 인것들도 모두 다른 경우로 취급해 엄청 큰 값이 나온다. 

각 동전의 경우에 따른 경우의 갯수를 cache의 2차원 배열로 추가하면 문제 해결이 가능하고, 실제로도 이렇게 답을 구했었지만 이 경우 문제의 메모리 제한 4mb를 초과해버린다.

#include <cstdio> #include <cstring> int n,coin[101],k,cache[10001]; //사용한 동전 갯수가 같으면 같은 경우이다. int dp(int value){ //기저사례 num == k일때 if(value==0) return 1; //기저사례 2, n을 넘어설때, 0 if(value<0) return 0; int& ret = cache[value]; if(ret!=-1) return ret; ret = 0; //점화식 for(int i=0;i<n;i++){ if(value-coin[i] >= 0) ret = ret + dp(value-coin[i]); } return ret; } int main(){ memset(cache,-1,sizeof(cache)); scanf("%d %d",&n,&k); for(int i=0;i<n;i++){ scanf("%d",&coin[i]); } printf("%d",dp(k)); }


밑은 각 동전별로 경우의 수를 구한 코드이다.


#include <cstdio>
int n,coin[101],k,cache[10001]={0,};
int main(){
    scanf("%d %d",&n,&k);
    for(int i=0;i<n;i++){
        scanf("%d",&coin[i]);
    }
    cache[0] = 1;
    
    //각 동전별로 값마다 경우의 수를 구한다.     
    for(int i = 0; i<n; i++){
        for(int j = 0; j<=k;j++){
            if(j>=coin[i])
                cache[j] += cache[j-coin[i]];
        }
    }   
    printf("%d",cache[k]);
}

코드가 훨씬 간결하고 직관적이다. DP에 대한 학습은 아직 더 필요한거같다.

'Algorithm > Problems' 카테고리의 다른 글

백준 - 1912 연속합  (0) 2016.03.07
백준 - 2294 동전 2  (0) 2016.03.07
백준 - 1697 숨바꼭질  (1) 2016.02.28
백준 - 1005 ACM Craft  (0) 2016.02.27
더블릿 - 댐  (0) 2016.02.26


문제 링크


나는 BFS로 푼 문제이다. 시작 점이 꼭 1번 건물이 아닐 수도 있으므로 모든 건물의 정보를 입력 받는 과정에, 그 이전 단계가 있는 건물들은 시작 건물이 아니라 단정하고 시작 건물들이 가능한 건물들은 처음에 큐에 미리 넣어주고 BFS를 진행하였다.

이 문제에 함정이 있는 게, 문제는 최소 시간을 구하라고 하는데 따지고 보면 그냥 그 단계까지 누적 최고 건설 시간들만 구해주면 된다. 왜냐하면 특정 단계 건물을 짓기 위해서는 선행 건물들을 모두 지어야 하는데, 선행 건물들 중에 건설 시간이 짧은 것을 아무리 먼저 지어도 건설 시간이 긴 건물이 지어지기 전까지는 그 특정 단계의 건물을 지을 수 없기 때문이다. 

이 로직 대로 코드를 짰는데, 처음에 문제를 풀 때 시간 초과가 났었다. 뭐가 문제인가 고민을 해봤더니, 초기에는 build배열에서 첫 번째 인덱스는 현재 건물, 두 번째 인덱스는 다른 건물을 인덱스로 두고, 그 배열 값이 참이면 경로가 존재한다고 하고 풀이를 진행했었다.

이 과정에서 매 루프마다 그때의 건물 배열을 1부터, n까지 전부 다 경로 검사를 진행해야 하니 시간 초과가 일어난 것이다. 그래서 일일이 해당 인덱스에 저장하는 방법을 버리고 해당 건물이 가진 경로의 개수를 path 배열 에다가 저장하고, 배열의 1 인덱스부터 다음 경로들을 저장하는 방법을 택하니 시간 초과에서 벗어났다. 


밑은 위의 과정대로 작성한 코드이다.


#include <cstdio> #include <queue> #include <cstring> #include <algorithm> using namespace std; //각 건물의 인덱스 0번에 건설 시간을 저장한다. int t,build[1001][1001],isFirst[1001]={0,},value[1001],path[1001]; int x,y,n,k,w; queue<int> q; int bfs(){ int ret = 0; //시작점이 가능한 것들을 전부 큐에 넣는다. for(int i=1;i<=n;i++){ if(isFirst[i]==0){ q.push(i); //초기 빌드의 누적 비용을 건설 비용으로 초기화한다. value[i] = build[i][0]; } } //BFS를 진행한다. while(!q.empty()){ //큐에서 인덱스를 하나씩 꺼내서, 다음 경로에서 가능한 것을 찾는다. int p = q.front(); q.pop(); //원하는 건물에 도달하면, 최대 값을 넣어준다. if(p==w) ret = max(ret,value[p]); else{ for(int i=1;i<=path[p];i++){ if(build[p][i]>=1){ int a = build[p][i]; // 다음 빌드를 짓는 데에 드는 최대 비용을 구한다. 최대 비용이 안되면 enqueue 하지 않는다. if(value[a] < value[p]+build[a][0]){ value[a] =value[p] + build[a][0]; q.push(a); } } } } } return ret; } int main(){ scanf("%d",&t); while(t--){ //초기화// for(int i=0; i<q.size();i++) q.pop(); memset(build,0,sizeof(build)); memset(value,0,sizeof(value)); memset(isFirst,0,sizeof(isFirst)); memset(length,0,sizeof(length)); //건물의 갯수와 건설 규칙 순서 scanf("%d %d",&n,&k); //각 건물 0번 인덱스에 건물의 건설 시간을 저장한다. for(int i=1;i<=n;i++) scanf("%d",&build[i][0]); //각 건물의 경로 초기화 for(int i=0;i<k;i++){ scanf("%d %d",&x,&y); int a = ++path[x]; //해당 경로가 있음을 표시하기위해 1을 저장한다. build[x][a] = y; //또한 해당 경로가 시작점이 가능한지 아닌지도 체크를 해야된다. isFirst[y] = 1; } //목표 건물 scanf("%d",&w); printf("%d\n",bfs()); } }


변수가 쓸대 없이 많아진 것 같아 나중에 시간을 내서 리팩토링을 해야겠단 생각이 든다.


정답률이 15%라 엄청 긴장하고 풀었는데 그 정도 까지의 문제는 아니었던 것 같다.

'Algorithm > Problems' 카테고리의 다른 글

백준 - 2293 동전 1  (1) 2016.03.07
백준 - 1697 숨바꼭질  (1) 2016.02.28
더블릿 - 댐  (0) 2016.02.26
알고스팟 - NQUEEN, 백준 - 9663 N-QUEEN  (1) 2016.02.25
알고스팟 - MORSE, DICT  (0) 2016.02.25


문제 링크 : 알고스팟 NQUEEN

문제 링크 : 백준 N-QUEEN


N-Queen문제, 즉 여왕말 문제는 백트래킹 관련으로 정말 유명한 문제이다. 백트래킹이란 그냥 다음 탐색을 진행하는데, 그 탐색이 유망하지 않으면 무시하고 돌아가서다시 탐색한다는 것(백트래킹,되추적) 이다.

출처 위키피디아

여왕말에서 유망한 탐색인지 아닌지를 검사하는 조건은 정말 간단하다. 그냥 지금까지 고른 말의 위치 중에서 같은 열에 속하는 게 있는지, 혹은 대각선에 위치하는 게 있는지 검사하고 있으면 이 탐색을 무시하고(가지치기하고) 되돌아가 다음 탐색을 진행(백트래킹)한다.

끝까지 도달하였으면, 가능한 경우를 찾은 것이므로 카운트를 증가 시키고 반환하여 다른 경우를 찾는다.

밑은 이 풀이에 해당하는 코드이다.


#include <cstdio> #include <cmath> int n, t, a[13], count=0; //이 위치에 여왕 말을 놓을 수 있는지, 즉 유망한지 체크한다. bool check(int cur) { //0부터 이 행까지 모두 조사한다. for (int i = 0; i<cur; i++) { //열이 같은 것이 있거나, 대각선 범위 내에 있으면 불가능한 경우므로 false을 리턴 if(a[i]==a[cur] || cur-i==std::abs(a[cur]-a[i])){ return false; } } return true; } void nqueen(int cur) { //기저사례 : 끝에 도달하였으면 카운터를 증가 시키고 반환한다. if(cur==n-1) { count++; return; } //아직 끝에 도달하지 않았으면 다음 위치를 찾는다. for(int i=0;i<n;i++){ a[cur+1] = i; //이 위치가 유망한지 검사, check이 참이면 다음 여왕 말을 찾고, 못 찾으면 백트래킹한다. if(check(cur+1)){ nqueen(cur+1); } } } int main() { scanf("%d",&t); while(t--){ count = 0; scanf("%d", &n); nqueen(-1); printf("\n%d\n",count); } }


근데 이렇게 가지치며 나가는 것보다 더 빠른 방법이 있는 것 같다. 좀 더 공부가 필요한듯하다..

'Algorithm > Problems' 카테고리의 다른 글

백준 - 1005 ACM Craft  (0) 2016.02.27
더블릿 - 댐  (0) 2016.02.26
알고스팟 - MORSE, DICT  (0) 2016.02.25
백준 - 1963 소수경로  (0) 2016.02.24
백준 - 2960 에라토스테네스의 체  (1) 2016.02.24

+ Recent posts