Problem 93 : Arithmetic expressions (35%)
Description
original
By using each of the digits from the set, {1, 2, 3, 4}, exactly once, and making use of the four arithmetic operations (+, −, * , /) and brackets/parentheses, it is possible to form different positive integer targets.
For example,
8 = (4 * (1 + 3)) / 2 14 = 4 * (3 + 1 / 2) 19 = 4 * (2 + 3) − 1 36 = 3 * 4 * (2 + 1)
Note that concatenations of the digits, like 12 + 34, are not allowed.
Using the set, {1, 2, 3, 4}, it is possible to obtain thirty-one different target numbers of which 36 is the maximum, and each of the numbers 1 to 28 can be obtained before encountering the first non-expressible number.
Find the set of four distinct digits, a < b < c < d, for which the longest set of consecutive positive integers, 1 to n, can be obtained, giving your answer as a string: abcd.
간략해석
$a < b < c < d$인 한 자리 수를 4개를 원하는대로 배치하여, (+, -, * , /)와 괄호를 마음대로 사용하여 결과을 연산한다. 이때 1부터 연속된 자연수를 가장 많이 표현할 수 있는 $a, b, c, d$를 구하여라.
Idea & Algorithm
naive idea
우선 총 연산해야할 개수를 우선 구해보자. 우선 $a,b,c,d$의 조합은 Combination으로, 순서는 팩토리얼로, 각 위치 별로 들어갈 연산자는 지수승으로, 그리고 괄호의 순서가 있다.
여기서 조금 어려운 아이디어는 괄호인데, 괄호의 이유를 알면 쉽게 알 수 있다. 괄호는 다른 연산자보다 먼저 연산함을 의미한다. 즉 이는 4개 수 사이에 있는 연산자를 어떤 순서대로 하는가에 대한 문제이다. 그렇다면 이 가짓수는 팩토리얼로 구할 수 있다.
\[10C4 \cdot 4! \cdot 4^3 \cdot 3! = 1935360\]즉 1,935,360개의 결과값을 연산하면 된다. 어떤 방식으로 구현할 수 있을까.
advanced idea
이 문젠는 구현 연습과 같기에 구현 방법에 대해서 설명하고자 한다.
1. a, b, c, d 구하기 : dfs
a, b, c, d는 4중 포문 또는 재귀함수를 통해 쉽게 구할 수 있다.
재귀함수의 경우에는 1부터 시작하여 1을 포함하는 경우, 1을 포함하지 않는 경우로 나누어서 재귀를 돌리면 된다. 아래의 코드 경우에는 한자리수를 포함하느냐 마느냐이기에 수를 포함시킬때마다 * 10 + idx를 하여 수를 넘겼고 네자리수인 경우 네 수가 포함된 것이니 $a,b,c,d$를 골랐다는 것을 알 수 있다. idx같은 경우는 9까지 포함시키도록 조건문을 포함시켰다.
2. 네 수 순서쌍 고르기 : ck
이 부분은 c++ algorithm헤더의 next_permutation을 사용하였다. 이 함수는 정렬된 배열 또는 벡터에서 다음 순열로 바꿔주는 함수이다. 연산자의 순서 경우에도 후에 이 함수를 사용하였다.
3. 고정된 순서쌍에서 연산값 구하기 : calc
이 경우에는 연산순서와 연산자를 선택하면된다. 연산순서는 위에 설명하였고, 연산자의 종류는 0,1,2,3으로 지정하여 3중 포문으로 케이스르 구하였다.
4. 순서쌍, 연산순서, 연산자가 결정된 식 계산 : calc_detail
이 경우에는 연결리스트처럼 연산을 실행하였는데, v[idx]과 v[idx+1]을 연산하면 v[idx]에 연산결과를 저장하고, v[idx+1] 뒤의 모든 값을 v[i] = v[i+1]과 같은 연산으로 한칸씩 이동시킨다.
이렇게 연산을 하면 v[0]의 값이 연산 결과값이 된다. 단, 주의할 점은 앞에 부분을 연산했을 때, 후에 idx 부분의 처리를 해주어야한다. 앞에 연산한 횟수만큼 -1를 해주어야한다.
5. 연산하기 : op
연산 종류에 따른 번호와 연산할 값을 입력받고 연산결과를 리턴한다. 단 divided by 0에러를 예외처리 해야하고, 이 경우 에러값으로 리턴하였다.
그외
연산에 따른 결과 값은 set에 저장하였고, set에서 iterate하여 합하고 체크하였다. 또한 이 결과값은 중간에 double형이 될 수 있으므로 double로 연산을 하였다. (int)x == x인 경우에 대해서만 set에 insert하였다.
source code
아래는 위 설명에 따른 소스코드다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
int ans, len;
const int ERROR_CHECK = 1e5;
double op(int i, double a, double b){
if(i==0) return a+b;
if(i==1) return a-b;
if(i==2) return a*b;
if(i==3 && b != 0) return a/b;
return ERROR_CHECK;
}
int calc_detail(vector<double> v, vector<int> order, vector<int> oper){
for(int i = 0 ; i < 3 ; i++){
int ord = order[i];
for(int j = 0 ; j < i ; j++){
if(order[j]<order[i]) ord--;
}
v[ord] = op(oper[i],v[ord],v[ord+1]);
if(v[ord]==ERROR_CHECK) return 0;
for(int j = ord+1 ; j < 3 ; j++){
v[j] = v[j+1];
}
}
if((int)v[0] != v[0] || v[0] <= 0) return 0;
return (int)v[0];
}
set<int> calc(vector<double> v){
vector<int> order;
for(int i = 0 ; i < 3 ; i++) order.push_back(i);
set<int> s;
do{
for(int i = 0 ; i < 4 ; i++){
for(int j = 0 ; j < 4 ; j++){
for(int k = 0 ; k < 4 ; k++){
vector<int> oper;
oper.push_back(i);
oper.push_back(j);
oper.push_back(k);
s.insert(calc_detail(v, order, oper));
}
}
}
}while(next_permutation(order.begin(),order.end()));
return s;
}
void ck(int k){
int val = k;
vector<double> v;
while(k){
v.push_back((double)(k%10));
k /= 10;
}
sort(v.begin(), v.end());
set<int> s;
do{
set<int> tmp = calc(v);
for(int i : tmp) s.insert(i);
}while(next_permutation(v.begin(),v.end()));
for(int i = 1 ; ; i++){
if(s.count(i)==0){
if(i>len){
ans = val;
len = i;
}
return ;
}
}
}
void dfs(int idx, int val){
if(idx==11) return ;
if(val>=1000){
ck(val);
return ;
}
dfs(idx+1, val*10+idx);
dfs(idx+1, val);
}
int main(){
dfs(1,0);
cout << ans;
}
Leave a Comment