본문 바로가기

UCPC

[20/07/12] ucpc 연습문제

2020 / 07 / 03 (금) 코드포스 연습문제

후기

전에 공부했던 개념이 많이 나와서 좋았다! 항상 처음 접하는 개념만 나온다고 생각했는데 내가 풀었던 내용이 다시 나오니 확실히 빨리 풀 수 있었다.

A 수열과 시프트 쿼리 (17499)

image

 

 

혼자서 풀어보려하다가 자꾸 꼬여서 북님 코드 참고했다. 난 offset을 기존 배열을 이동한 만큼을 저장하려 했는데 그것보다 출력할 때 i + offset을 출력하면 되게 만드는 게 더 쉬운 것 같다. 인덱스 같은 경우는 n이 넘어가면 안 되므로 n을 나눈 나머지를 저장해야 한다. 전에 원형 큐를 공부할 때 인덱스를 넘기지 않게 하기 위해 n을 나눠서 인덱스를 접근했는데 이와 비슷한 것 같다.

 

더보기
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <string>
#include <queue>
#include <stack>
#include <set>
#include <cstring>
#include <map>
#define xx first
#define yy second
 
using namespace std;
using i64 = long long;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;


int main() 
{
    int n, q;
    scanf("%d %d", &n, &q);
    
    vector<int> v(n);

    for (int i = 0; i < n; i++)
        scanf("%d", &v[i]);

    int offset = 0;

    for (int i = 0; i < q; i++)
    {
        int t;
        scanf("%d", &t);

        if (t == 1)
        {
            int p, x;
            scanf("%d %d", &p, &x);
            p--;
            p = (p + offset) % n;
            v[p] += x;
        }
        else
        {
            int s;
            scanf("%d", &s);
            if (t == 2)
                s = -s;
            offset = (offset + n + s) % n;
        }
    }

    for (int i = 0; i < n; i++)
        printf("%d ", v[(offset + i) % n]);

    return 0;
}

B 4연산 (14395)

image

 

 

dfs를 사용해서 연산의 최소 횟수를 구하는 문제. bfs.. 낯설지만 ucpc로 강제로 친해지는 중. bfs를 풀다가 전에 풀던 다익스트라 문제도 최단거리 구할 때 썼던 것 같은데 무슨 차이일까 싶었다. 알고보니 edge에 weight가 있다면 다익스트라로 풀고 weight가 1이면 bfs로 푸는 것 같다. 이 문제처럼 최단거리를 거치는 경로를 물어보는 문제를 역추적 문제라고 한다.

 

더보기
#include <stdio.h>
#include <vector>
#include <queue>
#include <algorithm>
#include <iostream>
#include <string>
#include <bitset>
#include <map>
#include <set>
#include <tuple>
#include <string.h>
#include <math.h>
#include <random>
#include <functional>
#include <assert.h>
#include <math.h>
#define all(x) (x).begin(), (x).end()
#define xx first
#define yy second

using namespace std;

template<typename T, typename Pr = less<T>>
using pq = priority_queue<T, vector<T>, Pr>;
using i64 = long long int;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;

int main()
{
	i64 s, t;
	scanf("%lld %lld", &s, &t);
	
	if (s == t)
	{
	    printf("0\n");
	    return 0;
	}
	
	map<i64, pair<i64, char>> visited;
	visited[s] = pair<i64, char>(s, '.');
	
	queue<i64> q;
	q.push(s);
	
	while (!q.empty())
	{
	    auto now = q.front();
	    q.pop();
	    
	    if (now * now <= t && visited.find(now * now) == visited.end())
	    {
	        visited[now * now] = pair<i64, char>(now, '*');
	        q.push(now*now);
	    }
	    
	    if (now + now <= t && visited.find(now + now) == visited.end())
	    {
	        visited[now + now] = pair<i64, char>(now, '+');
	        q.push(now+now);
	    }
	    
	    if (now / now <= t && visited.find(now / now) == visited.end())
	    {
	        visited[now / now] = pair<i64, char>(now, '/');
	        q.push(now/now);
	    }
	}
	
	if (visited.find(t) == visited.end())
	{
	    printf("-1");
	    return 0;
	}
	
	i64 last = t;
	string ans;
	
	while (last != s)
	{
	    ans.push_back(visited[last].yy);
	    last = visited[last].xx;
	}
	
	reverse(all(ans));
	
	cout << ans << "\n";

	return 0;
}

 

 

image

 

 

이 문제에서 시간복잡도를 구하는 게 애매했다. bfs의 시간복잡도는 e + v이지만 v가 몇 개인지 알 수 없기 때문이다. 그래서 대략적으로 얼마나 걸릴지 계산해보았다.

 

 

 

C 학생 번호 (1235)

 

학생 수가 최대 1000이고 문자열의 길이가 100보다 작거나 같다. 최대 십만 번 반복하므로 완전탐색해도 시간 내에 돌아갈 것이다. 그래서 substr을 사용해서 뒷자리부터 학번을 자르고 중복인 학번을 제거한 다음 남은 수의 개수가 n과 같은지 확인했다.

더보기
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <string>
#include <queue>
#include <stack>
#include <set>
#include <cstring>
#include <map>
#define xx first
#define yy second
 
using namespace std;
using i64 = long long;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;


int main() 
{
    int n;
    scanf("%d", &n);

    vector<string> v(n);
    for (int i = 0; i < n; i++)
        cin >> v[i];

    int s_size = v[0].size();
    for (int k = 1; k <= s_size; k++)
    {
        vector<string> s(n);
        for (int i = 0; i < n; i++)
            s[i] = v[i].substr(s_size-k);

        sort(s.begin(), s.end());
        s.erase(unique(s.begin(), s.end()), s.end());
        if (s.size() == n)
        {
            printf("%d\n", k);
            return 0;
        }
    }
    printf("%d\n", n);
    return 0;
}

D 집 구하기 (13911)

 

다익스트라! 저번 ucpc 연습 때 다익스트라를 사용해야 하는 문제가 나와서 공부했었다. 이번 문제에 또 나와서 조금 편했음ㅋㅋ 이 문제는 맥도날드와 스타벅스까지의 최단거리를 구한 다음 x, y를 만족하는 노드 중 최단 노드를 구하면 되는 문제이다. 처음에 x를 만족하는 노드와 거리를 구하고 y를 만족하는 노드와 거리를 따로 저장한 다음 두 pair벡터의 교집합을 구하려 했는데 이러려면 n^2이 된다. 그래서 x, y를 따로 구분하지 않고 1부터 n까지 dist 배열을 돌면서 x, y이하인 점을 찾고 최솟값을 저장했다. 다 풀고 지금 드는 생각인데 집이 존재하지 않으면 -1을 출력해야 했다. 따로 지정해주지 않았는데 왜 맞았나 싶었더니 최솟값을 초기에 -1로 설정해줘서 그런 것 같다. 맞아서 다행이다.

 

더보기
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <string>
#include <queue>
#include <stack>
#include <set>
#include <cstring>
#include <map>
#define xx first
#define yy second
 
using namespace std;
using i64 = long long;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;


struct Edge
{
    Edge(int to_, int w_) : to(to_), w(w_) {}
    int to, w;
};

vector<Edge> edge[300005];
bool visited[300005];

vector<int> calc(int v, int x, vector<int> m)
{
    vector<int> dist(v+1, -1);
    memset(visited, false, sizeof(visited));
    
    priority_queue<ii, vector<ii>, greater<ii>> pq;
    
    for (int i = 0; i < m.size(); i++)
    {
        dist[m[i]] = 0;
        pq.emplace(0, m[i]);
    }
    
    while (!pq.empty())
    {
        
        auto now = pq.top();
        pq.pop();
        
        if (visited[now.yy])
            continue;
        
        visited[now.yy] = true;
        
        for (auto & e : edge[now.yy])
        {
            int newD = dist[now.yy] + e.w;
            
            if (dist[e.to] != -1 && dist[e.to] < newD)
                continue;
            
            dist[e.to] = newD;
            pq.emplace(dist[e.to], e.to);
        }
    }
    
    return dist;
}

int main() 
{
    int v, e;
    scanf("%d %d", &v, &e);
    
    for (int i = 1; i <= e; i++)
    {
        int a, b, t;
        scanf("%d %d %d", &a, &b, &t);
        
        edge[a].emplace_back(b, t);
        edge[b].emplace_back(a, t);
    }

    int m, x;
    scanf("%d %d", &m, &x);
    vector<int> vm(m);
    for (int i = 0; i < m; i++)
        scanf("%d", &vm[i]);

    int s, y;
    scanf("%d %d", &s, &y);
    vector<int> vs(s);
    for (int i = 0; i < s; i++)
        scanf("%d", &vs[i]);
        
    vector<int> mdist;
    vector<int> sdist;

    mdist = calc(v, x, vm);
    sdist = calc(v, y, vs);
    
    i64 min = -1;
    for (int i = 1; i <= v; i++)
    {
        if (!(0 < mdist[i] && mdist[i] <= x && 0 < sdist[i] && sdist[i] <= y))
            continue ;

        if (min == -1 || mdist[i] + sdist[i] < min)
            min = mdist[i] + sdist[i];
    }
    
    printf("%d", min);
    return 0;
}

 

    for (int i = 1; i <= v; i++)
    {
        if (0 < mdist[i] && mdist[i] <= x && 0 < sdist[i] && sdist[i] <= y)
        {
            if (min == -1)
                min = mdist[i] + sdist[i];
            else
            {
                if (mdist[i] + sdist[i] < min)
                    min = mdist[i] + sdist[i];
            }
        }
    }

 

처음 짤 떄 if문을 이렇게 설정했다. 지금 보니 똑같은 기능을 if 문 두 개로 나눴구나. if문은 되도록 중첩을 안 하는 게 좋아서 아래처럼 구현했다. 3중 if문을 하나로 나눔..

 

    for (int i = 1; i <= v; i++)
    {
        if (!(0 < mdist[i] && mdist[i] <= x && 0 < sdist[i] && sdist[i] <= y))
            continue ;

        if (min == -1 || mdist[i] + sdist[i] < min)
            min = mdist[i] + sdist[i];
    }

 

E 겹치는 선분 (1689)

 

처음에 이 문제를 좌표 압축으로 풀어보려 했었다. 선분의 개수가 1000000개이므로 좌표압축을 해서 선형으로 풀면 되지 않을까? 싶었는데 O(N)만에 못 풀고 N^2으로 해서 해당 영역을 세어야 했다. 이 문제는 해당 구간 내의 값은 상관 없고 구간이 시작할 때 1을 세어주고 끝날 때 1을 감소시켜 주면 된다. 그러므로 시작과 끝을 따로 저장을 해서 시작이면 +1을 끝나면 -1을 해주도록 저장한 다음, 시점을 전부 정렬해서 세어주면 된다. 문제에서 선분의 끝 점에서 겹치는 것은 세지 않는다 해서 난 이전 점을 저장한 다음 점이 바뀔 때 세도록 했었다. 이건 먼저 감소 시킨 다음 증가 시키면 해결할 수 있다. pair로 정렬하면 자동으로 감소하는 것이 먼저오므로 이 부분은 상관하지 않고 계산하면 된다.

더보기
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <string>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define xx first
#define yy second
#define all(x) (x).begin(), (x).end()
 
using namespace std;
using i64 = long long;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;


int main() {
    int n;
    scanf("%d", &n);
    
    vector<ii> event;
    for (int i = 0; i < n; i++)
    {
        int x, y;
        scanf("%d %d", &x, &y);
        event.emplace_back(x, 1);
        event.emplace_back(y, -1);
    }
        
    sort(all(event));

    int count = 0;
    int max = 0;
    for(int i = 0; i < event.size(); i++)
    {
        count += event[i].yy;
        if (max < count)
        {
            max = count;
        }
    }
    
    printf("%d\n", max);
    
    return 0;
}

F 삼삼한 수 (17252)

 

숫자가 작은 것 같아서 부분집합을 만들어서 풀 수 있을 것 같았다. 계산해보니 21억보다 작은 수 중 가장 큰 3의 제곱은 19제곱이었다. 0부터 19까지 3의 제곱수를 미리 구한 다음에 이 배열의 부분집합을 만들어서 모든 삼삼한 수를 만들었다. 혹시 메모리 초과가 날 까봐 모든 삼삼한 수의 개수를 확인해보았더니 대략 천만 개였다. 메모리 제한에 걸리지 않아서 다행이다. 다음으로 입력을 받고 배열 안에 삼삼한 수가 있다면 YES를 아니면 NO를 출력한다. 저번주에 부분집합 만드는 걸 배웠는데 오늘 써먹어서 뿌듯했다ㅋㅋ

 

더보기
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <string>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define xx first
#define yy second
 
using namespace std;
using i64 = long long;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;
 
int main() {
    int n;
    scanf("%d", &n);
    
    if (n == 0)
    {
        printf("NO");
        return 0;
    }
    
    i64 num = 1;
    
    vector<i64> t(20);
    for (int i = 0; i <= 19; i++)
    {
        t[i] = num;
        num *= 3;
    }
    
    vector<i64> ans;
    for (int i = 0; i < (1 << 20); ++i) 
    { 
        i64 sum = 0;
        for (int j = 0; j < 20; ++j)
        { 
            if (i & (1 << j)) 
                sum += t[j];
        }
        ans.push_back(sum);
    }
    
    if (find(ans.begin(), ans.end(), n) != ans.end())
        printf("YES");
    else
        printf("NO");
    return 0;
}

 

G 시간 관리 (1263)

 

마감 시간이 가장 긴 것부터 차례대로 구했다. 전에 비슷한 문제를 풀었는데 그 떄도 끝나는 시간 순서로 풀었던 게 기억났다. 아무튼.. 마감 시간이 가장 깉 것부터 수행하고 다음으로 긴 걸 수행하고 하다가 모든 일이 다 끝나는 시점이 0보다 작으면 -1을 출력했다.

 

더보기
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <string>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define xx first
#define yy second
#define all(x) (x).begin(), (x).end()
 
using namespace std;
using i64 = long long;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;

bool sortbyfirst(const pair<int,int> &a, const pair<int,int> &b) 
{ 
    return (a.yy > b.yy); 
}

int main() {
    int n;
    scanf("%d", &n);
    
    vector<ii> v(n);
    for (int i = 0; i < n; i++)
        scanf("%d %d", &v[i].xx, &v[i].yy);
        
    sort(v.begin(), v.end(), sortbyfirst);
    
    int start = v[0].yy - v[0].xx;
    for (int i = 1; i < n; i++)
    {
        if (v[i].yy < start)
            start = v[i].yy;
        start -= v[i].xx;
    }
    
    if (start < 0)
        printf("-1");
    else
        printf("%d", start);
    
    return 0;
}

 

'UCPC' 카테고리의 다른 글

UCPC 2021 예선 후기  (6) 2021.08.01
UCPC 2020 후기  (0) 2020.08.01
[20/07/05] G. 개업 2 (13902)  (0) 2020.07.10
[20/07/05] I. 수학 숙제 (2870)  (0) 2020.07.09
[20/07/05] H. 표절 (2428)  (0) 2020.07.09