Algorithm

'알고리즘'에 해당되는 글 27건

  1. BOJ 2293 - 동전 1 2
  2. BOJ 5893 - 17배
  3. Codeforces 659B - Qualifying Contest
  4. BOJ 2178 - 미로 탐색 2
  5. BOJ 1149 - RGB 거리

BOJ 2293 - 동전 1

알고리즘/동적 계획법

문제



풀이

Coin Change라는 이름으로 유명한 DP 문제이다. 이 문제는 2차원 배열을 사용해서 해결할수도 있고 1차원 배열을 사용해서 해결할수도 있는데 2차원 배열 사용하면 6MB 정도의 메모리를 사용하게 되므로 문제의 메모리 제한을 초과하게 된다. 


일단 2차원 풀이법부터 설명해보자면, dp[n][k] = n 종류의 동전으로 k원을 만드는 경우의 수라고 정의할때 dp[n][k]를 구하는 방법은 두가지이다.


1. dp[n][k-coin[n]] = n번째 동전을 사용한 뒤 k원을 만들수 있는 경우의 수

2. dp[n-1][k] = n번째 동전을 사용하지 않고 k원을 만들수 있는 경우의 수


이렇게 되므로 점화식은 $ dp[n][k] = dp[n][k-coin[n]] + dp[n-1][k] $가 된다.

#include<stdio.h>
#include<string.h>
int coin[100]={0},cache[100][10001];
int solve(int n,int k){
	if(n<0||k<0)return 0;
	if(k==0) return 1;
	if(cache[n][k] != -1) return cache[n][k];
	return cache[n][k] = solve(n,k-coin[n]) + solve(n-1,k);
	
}
int main() {
	int n,k,i;
	scanf("%d%d",&n,&k);
	for(i=0;i<n;i++){
		memset(cache[i],-1,sizeof(cache[i]));
		scanf("%d",&coin[i]);
	}
	printf("%d",solve(n-1,k));
}


물론 위는 2차원 배열을 사용해서 메모리 초과가 발생한다. 1차원 풀이는 아무리 생각해도 모르겠어서 여러 문서를 뒤져보고 이해했다.


2차원 배열을 사용하는 점화식이 $ dp[n][k] = dp[n][k-coin[n]] + dp[n-1][k] $인데, 여기서 n이 1증가하면 결국은 $ dp[n][k] = dp[n][k-coin[n]] $이되므로 $ \sum\limits_{i=coin[n]}^k cache[i] = cache[i] + cache[i - coin[n]] $이 된다.

#include <stdio.h>
int main(void) {
	int i,j,n,k,coin[100]={0},cache[10001]={0};
	scanf("%d%d",&n,&k);
	cache[0]=1;
	for(i=0;i<n;i++){
		scanf("%d",&coin[i]);
		for(j=coin[i];j<=k;j++)
			cache[j] += cache[j-coin[i]];
	}
	printf("%d",cache[k]);
	return 0;
}


BOJ 5893 - 17배

알고리즘/문자열 처리

문제


https://www.acmicpc.net/problem/5893


풀이


들어오는 이진수의 길이의 최대가 1000자리라고 했으니 십진수로 변환하면 최대 $2^{1001}-1$ 값이 들어온다는 의미이므로 long long을 이용하더라도 범위를 초과하므로 해결 할 수가 없다. 게다가 C언어에서는 직접 2진수 인풋을 받을 수 없으므로 문자열로 입력받아 처리하는게 제일 간편할것이다.


쉬프트 연산으로 17배는 $ x << 4 + x $ 이므로 똑같이 계산해주면 된다.


문자열이므로 가장 왼쪽(0번 인덱스)은 직접 계산을 하지않았는데 계산하지 않은 0번 인덱스는 항상 1~4 사이의 값이 나오므로 계산 후에 [0]의 문자가 무엇인지 보고 직접 출력하게 했다.

#include <iostream>
#include <string>
using namespace std;
int main() {
	int i=0,respos;
	string s,shifted,res,z="";
	cin>>s;
	res = shifted = s +"0000"; //왼쪽으로 << 4 연산;
	respos=res.size();
	for(i=s.size()-1;i>=0;i--){
		respos--;
		if(s[i]=='0')  continue;
		res[respos]++;
	}
	for(i=res.size()-1;i>0;i--){
		if(res[i]>='2'){
			res[i-1] += (res[i]-'0')/2;
			res[i] = ((res[i]-'0')%2)+'0';

		}
	}
	if(res[0]=='2') res[0]='0',cout<<"1";
	else if(res[0]=='3') res[0]='1',cout<<"1";
	else if(res[0]=='4') res[0]='0',cout<<"10";
	cout<<res;
}


Codeforces 659B - Qualifying Contest

알고리즘/정렬

문제




대략 해석해보자면 전국 프로그래밍 대회가 열리는데 그 전에 지역마다 예선을 치뤄서 각 지역마다 대표 2명을 뽑아서 참가하게 하려는데 대표 2명을 구성할수 있는 경우가 유니크(단 한 가지)한지 판별해야 하는 문제이다.


입력은 첫 줄에 전체 학생 수 N과 전체 지역 수 M이 들어오고 2번째 줄부터 N+1번째 줄까지 이름, 지역 번호, 점수(0~800)가 들어온다.

우리는 그 데이터를 가지고 각 지역의 대표를 구성할수 있는 경우가 유니크한지, 유니크 하다면 점수->이름(Sort by score, then by name)순으로 정렬된 대표들을 출력하는 것이다. 만약에 유니크하지 않는다면 ?를 출력하면 된다.


풀이


배열에 이름과 점수를 모두 가지고 정렬을 해야하므로 map을 사용할까 set을 사용할까 생각하다가 그냥 vector<pair<string,int>>를 사용하기로 결정하고 해당 vector를 배열로 만들어 vector[지역번호] 식으로 사용하였다.


학생의 이름, 지역 번호, 점수를 받은 뒤 vector[지역 번호].push_back(make_pair(이름, 점수)) 식으로 데이터를 넣었고 정렬은 <algorithm>에 있는 stable_sort를 이용하여 직접 점수 정렬 후 이름 정렬을 구현 하였다.


또한 각 팀마다 같은 점수가 몇 명 있는지 확인하기 위해 hi라는 배열을 만들어 기저 사례를 처리하였다.

기저 사례

1. 가장 높은 점수가 2명이면 바로 해당 학생들의 이름을 출력
2. 가장 높은 점수가 3명 이상 있거나 한 지역의 참가 학생이 2명보다 적으면 ?를 출력

또한 가장 높은 점수가 1명이더라도 두번째로 높은 점수가 여러명이면 유니크한 경우가 아니므로 확인 후 ?를 출력하게 하였다.


#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#define INT_MAX 987654321
using namespace std;

bool cmp(const pair<string,int>& val1, const pair<string,int>& val2){
	if(val1.second!=val2.second)
		return val1.second>val2.second;
	else
		return val1.first<val2.first;
}

int main()
{
	int n,m,i,j,**hi;
	vector<pair<string,int>> *v;

	cin.sync_with_stdio(false);
	cin>>n>>m;
	v = new vector<pair<string,int>>[m+1];
	hi = new int*[m+1];

	for(i=1;i<=m;i++){
		hi[i] = new int[801]();
	}

	for(i=0;i<n;i++){
		string name;
		int team,score;

		cin>>name>>team>>score;

		v[team].push_back(make_pair(name,score));
		hi[team][score]++;
	}

	for(i=1;i<=m;i++){
		stable_sort(v[i].begin(),v[i].end(),cmp);

		if(hi[i][v[i][0].second]>2 || v[i].size() < 2) {
			cout << "?\n";
		}
		else {      
			if(hi[i][v[i][0].second] == 2)
			{
				cout << v[i][0].first << " " << v[i][1].first << "\n";
				continue;
			}

			int secondary = INT_MAX;

			for(j=0;j<v[i].size();j++){
				if(v[i][0].second != v[i][j].second){
					secondary = v[i][j].second;
					break;
				}
			}

			if(secondary != INT_MAX && hi[i][secondary]>1)
				cout << "?\n";
			else
				cout << v[i][0].first << " " << v[i][1].first << "\n";
		}
	}
}


BOJ 2178 - 미로 탐색

알고리즘/DFS∕BFS

문제




간단한 BFS를 이용한 길 찾기 문제이다. 알고리즘 분류는 BFS로 되어있는데 당연히 DFS로도 된다. 나는 BFS로 구현했다.


소스


#include <iostream>
#include <queue>
using namespace std;

struct pt{
	int x;
	int y;
};

int map[501][101] = {0};
int visit[501][101] = {0};
queue<pt> q;

void setVisit(int x, int y, pt o){
	pt next;
	next.x = x;
	next.y = y;
	q.push(next);
	visit[y][x]+=visit[o.y][o.x]+1;
}
int main(){
	
	int n,m,i,j;char c;
	pt o;
	cin>>n>>m;
	for(i=0;i<n;i++){
		for(j=0;j<m;j++) {
			cin >> c;
			map[i][j]=c-'0';
		}
	}
	o.x=0;o.y=0;
	q.push(o);
	visit[0][0]=1;
	while(!q.empty()){
		pt front = q.front();
		q.pop();
		if(front.x-1 >= 0 && !visit[front.y][front.x-1] && map[front.y][front.x-1])
			setVisit(front.x-1,front.y,front);
		if(front.x+1 < m && !visit[front.y][front.x+1] && map[front.y][front.x+1])
			setVisit(front.x+1,front.y,front);
		if(front.y-1 >= 0 && !visit[front.y-1][front.x] && map[front.y-1][front.x])
			setVisit(front.x,front.y-1,front);
		if(front.y+1 < n && !visit[front.y+1][front.x] && map[front.y+1][front.x])
			setVisit(front.x,front.y+1,front);
	}
	cout << visit[n-1][m-1] << '\n';
}


각 줄마다 띄어쓰기 없이 미로가 주어지므로 char 형으로 받은 다음 '0'(48)을 빼주어 정수형으로 바꾸어주었다.


큐에는 이번에 탐색해야할 좌표 값을 저장해야하므로 x,y값을 가지는 구조체를 만들어 좌표값을 저장시켜 주었고 맨 처음 시작할때 0,0 좌표 변수를 만들어 큐에 넣어주고 시작하게 했다.


탐색을 할때 상하좌우에 이동 할 수 있으며 방문하지 않은곳이 있다면 방문을 했다는 표시를 하고(어차피 큐에 넣고 나중에 방문할것이니 미리 표시해도 상관이 없다) visit[y][x]변수에 몇 번 이도해서 x,y 좌표에 왔는지에 대한 횟수를 적어두게하였다.

BOJ 1149 - RGB 거리

알고리즘/동적 계획법

문제


https://www.acmicpc.net/problem/1149



전형적 DP 문제이지만 내가 너무 초보라 처음 풀었을때 엄청 헤맸다. 모든 집을 R,G,B 색 중 하나로 칠하는데 각 집들을 도색하는데 이웃과 색상이 같으면 안되는 조건 하에 도색 할수 있는 최소 비용을 구하는 문제이다.


풀이


각 집을 도색할때 이전 집과 색상이 같지않게 처리하면 모든 이웃이 색상이 같이 않게 되기 때문에 내가 R일때는 이전 집의 G or B의 비용 중 최소값, 내가 G일때는 R or B 중 최소값, 내가 B일때는 R or G 중의 최소값에 현재 집을 칠할 색의 비용을 계속 더해가주면 된다.


$ Cost[i][j] = min( Cost[i-1][(j+1)\%3], Cost[i-1][(j+2)\%3] ) + Home[j] $

Cost[i][j] = i번째집을 j색상으로 칠하려할때 i번째 집까지 도색할때 드는 최소 비용

Home[j] = 현재 집을 j색상으로 칠하려 할때 드는 비용


#include <stdio.h>

int cache[1001][3];

int min(int x, int y){ return (x>y)?y:x; }

int main(void) {
	int i,j,t,r[3];
	scanf("%d",&t);
	for(i=1;i<=t;i++){
		scanf("%d%d%d",&r[0],&r[1],&r[2]);
		for(j=0;j<3;j++)
			cache[i][j] = min(cache[i-1][(j+1)%3],cache[i-1][(j+2)%3])+r[j];
	}
	printf("%d",min(min(cache[t][0],cache[t][1]),cache[t][2]));
	return 0;
}


주의

% 연산자보다 + 연산자의 우선순위가 낮기 때문에 j + 2 % 3의 결과가 j + (2 % 3)가 되므로 직접 괄호로 (j + 2) % 3 이렇게 우선순위를 정해주어야 한다.