본문 바로가기

대회/KOI

KOI 2023 2차 대회 중등부 후기

현재 가채점 결과가 나왔다.

3위.

중간에 충전선에 아이패드가 걸려서 카메라가 넘어진 것 때문에 부정행위에 걸리지 않는 이상 3위는 고정일듯 하다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

타임라인 1줄요약

1->2->3긁기->4긁기->3->4긁기

아래부터 나와있는 풀이의 내용은 오류가 있을 수 있다. (부등호 실수, 조건 빠뜨림 등등?)

 

0:00 ~ 8:26 : 1번 해결 +100점

 

1번은 문제해석이 중요하다. 문제의 조건을 다 보지 않으면 예제 2의 답이 이상하게 느껴지기 때문이다.

실제 공지

문제를 요약하면 다음과 같다.

스케이트를 속력 0에서 시작하여, N개의 지점에서 아래와 같은 속력 조건에 맞춰 속력을 조정한 후 마지막에는 속력 0으로 끝날 때, 각 부분에서의 속력의 합의 최댓값은? (=N개의 지점에서의 속력의 합)
조건 1 : 배열 A = (a1, a2, a3, ..., an)에 대하여 i번 지점에서의 속력은 ai 이하이다.
조건 2 : 다음 지점으로 넘어갈 때 속력은 임의의 값만큼 올릴 수 있고, 낮출 때는 1씩만 가능하다.

이 문제에서 빠뜨리기 쉬운 조건은 속력 0에서 시작하고 0으로 끝난다는 것이다.

어쨌든 조건을 보면 생각하기 은근 까다롭다는 것을 알 수 있다. 여기서 반대로 생각해보자.

마지막 지점에서 출발하여, 첫번째 지점에서 속력 0으로 끝난다고 해보자. 그럼 조건 2를 다음과 같이 바꿀 수 있다.

조건 2 : 다음 지점으로 넘어갈 때 속력은 1만큼 올릴 수 있고, 낮출 때는 임의의 값만큼 가능하다.

이렇게 바꾸면 편리한 점은, 속력을 임의의 값만큼 낮출 수 있으므로 다음 지점으로 건너갈 때 속력이 속력 제한보다 크면, 그냥 속력 제한값으로 바꾸면 된다. 따라서 간단한 그리디 문제가 되며, 쉽게 해결 가능하다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
vector<ll> v;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
int main(){
	fast;
	cin>>n;
	v.resize(n);
	for(auto &i : v)cin>>i;
	reverse(v.begin(), v.end());
	ll ans = 0;
	ll cur_v = 0;
	for(int i = 0 ; i < n ; i++){
		cur_v = min(cur_v+1, v[i]);
		ans += cur_v;
	}
	cout<<ans;
}

1번은 solved.ac 기준 실버로 예상된다.

 

8:26 ~ 20:38 : 2번 해결 +100점

 

2번은 간단한, 그리고 한번쯤 풀어봤을? 유형의 dp 문제이다.

인접한 곳을 고를 수 없고, 원형이라는 점에서 https://www.acmicpc.net/problem/2482와 유사하다. (물론 약간 다르다.)

 

2482번: 색상환

첫째 줄에 N색상환에서 어떤 인접한 두 색도 동시에 선택하지 않고 K개의 색을 고를 수 있는 경우의 수를 1,000,000,003 (10억 3) 으로 나눈 나머지를 출력한다.

www.acmicpc.net

일단 마지막 위치를 생각하지 않고 그냥 선형 배열이라고 치자.

i번째 개미굴 위치에 있을 때, 고를 수 있는 방법은 다음과 같다.

1. 방 하나를 고른다. (단, i>1일때 i-1번 위치에서 방을 고르지 않고 쪽방을 골랐어야 한다.)

2. 쪽방을 모두 고른다. (모두 고르는 이유는 자명하다.)

따라서 다음과 같은 dp 배열을 고려한다.

dp[i][j] := i번 위치에서 이전 위치에서 방을 골랐는지, 쪽방을 골랐는지에 대한 bool 값이 j일때, 가능한 최댓값

1,2번을 고려하면 식은 쉽게 나온다. 이제 원형만 고려해 주면 되는데, 그냥 2차원을 3차원 dp로 바꾸면 된다.

dp[i][j][k] := i번 위치에서 이전 위치에서 방을 골랐는지, 쪽방을 골랐는지에 대한 bool 값이 j이고, j=1일때의 bool 값은 k일때, 가능한 최댓값

k가 추가된 이유는 마지막 n번 위치에서 방을 고를때는 1번 방이 영향을 주기 때문이다.

아래 코드는 0-index이다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll dp[252525][3][2];
ll n;
vector<ll> v;
ll f(ll x, ll is_1, ll is_prv){ //is_k가 1 : 이전에 쪽방, 0 : 이전에 방
    ll &ret = dp[x][is_1][is_prv];
    if(~ret)return ret;
    ret = 0;
    if(x==n-1){
        if(is_1 and is_prv){    //1, n-2 모두 쪽방=>어디든 가능
            ret = max(v[x], 1LL);
            return ret;
        }
        else{   //그 외 : 쪽방만 가능
            ret = v[x];
            return ret;
        }
    }
    if(x==0){
        ret = max(f(x+1, 1, 1)+v[x], f(x+1, 0, 0)+1);
    }
    else if(is_prv){
        ret = max(f(x+1, is_1, 1)+v[x], f(x+1, is_1, 0)+1);
    }
    else ret = f(x+1, is_1, 1)+v[x];
    return ret;
}
int main(){
    fast;
    cin>>n;
    v.resize(n);
    for(auto &i : v)cin>>i;
    memset(dp, -1, sizeof(dp));
    cout<<f(0,0,0);
}

 

2번은 boj 2482와 비슷한 티어인 G4~G3정도로 예상된다. (solved.ac 티어 기준)

작년보다 1,2번이 훨씬 쉬웠다. 작년 1번은 등차수열을 이용한 브루트포스(G5), 2번은 트리의 성질을 이용한 union-find + nC2(P5)였다.

그에 비해 이번 1,2번은 훨씬 간단한 것 같다.

 

20:38 ~ 40:31 : 3번 Subtask#1 해결 (S#1 : N,M1000) +5점

 

3번은 일단 문제 해석이 힘들었다. 꼬치를 왜 하필 +0.1이랑 +0.9에 꽃게 해놨는지도 모르겠다. 그냥 낚시인듯 하다.

(어차피 주어지는 점들의 좌표가 정수기 때문에, +0.1이든 +0.9든 +(π-3)이든 다 똑같다.)

 

문제를 보기 좋게 정리하면 다음과 같다. 

0~10^9 사이 수직선에 직선 N개가 있다. 각 직선은 weight를 가진다.
그 후 M개의 쿼리가 주어지는데, 각 쿼리는 점 2개로 이루어진다.
구간 [l,r]을 가지는 어떤 직선이, 좌표가 x인 점에 대하여 l≤x이고 x<r을 만족하면, 이 점은 이 직선에 포함된다고 하자.
1≤i≤N인 직선 i에 대하여, 직선 i가 주어진 점 중 정확히 1개를 포함하면, 그 직선은 그냥 사라진다.
직선 i가 주어진 점 중 정확히 2개를 포함하면, 그 직선은 weight를 주고 사라진다.
각 쿼리에서 지워진 직선들은 그 이후 쿼리에서도 지워진 상태가 유지된다.
이때 각 쿼리에서는, 점 2개를 포함해서 사라진 직선들의 weight 합을 출력하면 된다.
(모든 좌표는 정수, 1≤N,M≤250,000)

정리가 왜 이렇게 긴지는 모르겠지만, 대충 이해는 될 것이다. 보자마자 이 문제는 바로 풀기는 힘들겠다고 생각이 들어, 서브태스크를 긁기로 했다.

문제를 이해했다면, Subtask#1은 그냥 구현임을 알 수 있다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll n, m;
vector<pair<pair<ll,ll>,ll>> v;
int main(){
    fast;
    cin>>n>>m;
    for(int i = 0 ; i < n ; i++){
        ll a,b,c; cin>>a>>b>>c;
        v.push_back({{a,b},c});
    }
    while(m--){
        ll a,b; cin>>a>>b;
        vector<ll> chk(n+1, 0);
        ll ans = 0;
        for(int i = 0 ; i < n ; i++){
            auto [ran, w] = v[i];
            auto [l,r] = ran;
            if(l<=a and a<r)chk[i]++;
            if(l<=b and b<r)chk[i]++;
            if(chk[i]>0){
                v[i].first.first=v[i].first.second=-1;
                if(chk[i]==2)ans += w;
            }
        }
        cout<<ans<<"\n";
    }
}

 

40:31 ~ 54:08 : 3번 Subtask#2 해결 (S#2 : 각 직선이 차지하는 구간 크기는 5 이하이다.) +9점

 

각 직선이 포함하는 좌표가 최대 N*5개기 때문에, 각 직선이 포함하는 좌표를 그냥 저장하고, 점이 주어질때마다 그 좌표에 직선이 있는지를 확인하고, 직선을 삭제할 수 있으면 된다. 좌표압축을 하지 않고, 그냥 std::map같은 자료 구조에 넣으면 쉽게 구현할 수 있다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll n, m;
map<ll, set<ll>> mp;
vector<ll> v;
ll chk[252525];
pair<ll,ll> li[252525];
int main(){
    fast;
    cin>>n>>m;
    v.resize(n);
    ll c = 0;
    for(auto &i : v){
        ll a,b; cin>>a>>b;
        li[c]={a,b};
        cin>>i;
        for(int j = a ; j < b ; j++)mp[j].insert(c);
        c++;
    }
    while(m--){
        ll a,b; cin>>a>>b;
        set<ll> s;
        ll ans=0;
        for(auto e : mp[a]){
            chk[e]++;
            s.insert(e);
        }
        for(auto e : mp[b]){
            if(s.find(e)!=s.end()){
                ans += v[e];
            }
            chk[e]++;
            s.insert(e);
        }
        for(auto i : s){
            for(int j = a-6 ; j <= a+6 ; j++){
                if(mp[j].find(i)!=mp[j].end())mp[j].erase(i);
            }
            for(int j = b-6 ; j <= b+6 ; j++){
                if(mp[j].find(i)!=mp[j].end())mp[j].erase(i);
            }
        }
        cout<<ans<<"\n";
    }
}

 

54:08 ~ 64:03 : 3번 Subtask#3 해결 (S#3 : i번 직선은 i-1번 직선 구간에 포함된다.) +11점

 

Subtask#3은 그림으로 보면 어떤 상황인지 바로 알 수 있다.

아래부터 1,2,3,4번 직선이다.

색에 의미는 없다. 어쨌든 위와 같은 형태기 때문에, 알 수 있는 사실은 다음과 같다.

어떤 위치 x에 존재하는 점에 의하여 i번 직선이 삭제된다면, i-1번 직선도 삭제된다. (i>1)
pf)i번 직선의 구간은 [L_i, R_i]이고, i-1번 직선의 구간은 [L_i-1, R_i-1]이다. 이때 L_i-1<L_i, R_i-1>R_i인데, L_i≤x<R_i이므로, L_i-1≤x<R_i-1는 자명해진다.

i번 직선이 삭제된다면 i-1번 직선이 삭제된다? 이는 i번 직선이 삭제된다면 연쇄적으로 1번 직선까지 삭제된다는 것임을 알 수 있다.

따라서 어떤 점에 의하여 삭제되는 직선의 최대 번호만 구하면 되므로, 파라메트릭 서치를 고려하자.

f(x, y) := x번 직선이 y위치의 점에 의하여 삭제되는가?

이는 O(1)에 판별 가능하고, 이분탐색으로 x의 최댓값을 찾을 수 있다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll n, m;
map<ll, set<ll>> mp;
vector<ll> v;
pair<ll,ll> li[252525];
ll M;
ll chk(ll x, ll p){
    if(M>=x)return 1;
    if(li[x].first<=p and p<li[x].second)return 1;
    return 0;
}
int main(){
    fast;
    cin>>n>>m;
    v.resize(n);
    ll c = 0;
    for(auto &i : v){
        ll a,b; cin>>a>>b;
        li[c]={a,b};
        cin>>i;
        c++;
    }
    M=-1;
    while(m--){
        ll ans=0;
        ll a,b; cin>>a>>b;
        ll l = -1, r = n+1;
        while(l+1 < r){
            ll mid = l+r>>1;
            if(chk(mid, a))l = mid;
            else r = mid;
        }
        ll res = l;
        l = -1, r = n+1;
        while(l+1 < r){
            ll mid = l+r>>1;
            if(chk(mid, b))l = mid;
            else r = mid;
        }
        ll res2 = max(res, l);
        res = min(res, l);
        while(res>M){
            ans += v[M+1];
            M++;
        }
        M=max(M,res2);
        cout<<ans<<"\n";
    }
}

 

64:03 ~ 124:06 : 3번 Subtask#4 해결 (S#4 : 직선이 차지하는 구간 크기가 모두 같다) +23점

 

S#4부터는, 직선을 시작점을 기준으로(같다면 끝점을 기준으로) 오름차순으로 정렬하여 새로 번호를 매길 것이다.

각 직선의 구간 크기가 같다면, 무엇을 할 수 있을까? 그것은 바로 각 직선이 차지하는 구간의 크기를 s라 할때, 직선을 [L_i, L_i+s]로 나타낼 수 있다는 것이다. 또한, 직선을 정렬했기 때문에, 각 점에 의하여 삭제되는 직선을 구간으로 표현하면 그 구간이 연속적임을 알 수 있다.

따라서, 어떤 점에 의하여 삭제되는 가장 왼쪽 직선과, 가장 오른쪽 직선을 구하면 그 사이 직선을 싹 없애줄 수 있다.

어떤 점의 위치가 pos라고 하자. 다음과 같은 사실을 알 수 있다.

이 점에 의해 삭제되는 직선은, 0≤(pos와 L_i의 차이)<s인 모든 직선 i이다. (1≤i≤N)

이를 통해 알 수 있는 것은, pos-s<L_i≤pos를 만족하는 모든 직선 i가 사라짐을 알 수 있다.

직선이 오름차순으로 정렬되어 있으므로, 각 직선의 시작점 L_i에 대하여, pos-s의 upper_bound, pos의 upper_bound를 구하면, 삭제되는 직선을 알 수 있다.

이를 pos=a, pos=b인 경우에 대하여 모두 따진 후, Lazy propagation을 이용하여 구간 직선을 한꺼번에 처리할 수 있다.

솔루션은 꽤 명쾌하지만, 구현은 은근 어려워서 1시간을 날렸다. 차라리 풀이만 안상태에서 S#5를 건드리는게 나았을 것 같다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll n, m;
vector<pair<ll,ll>> v;
struct segtree{
    struct asdf{
        ll diff=0, lazy=0, chk=0;
    };
    vector<asdf> tree;
    segtree(): tree(2020202) {}
    void propagate(ll node, ll s, ll e){
        auto &[diff, lazy, chk] = tree[node];
        if(!chk)return;
        diff = (e-s+1)*lazy;
        if(s!=e)tree[node*2].lazy = lazy, tree[node*2].chk=1, tree[node*2+1].lazy = lazy;
        lazy=0, tree[node*2+1].chk=1;
        chk=0;
    }
    void upd(ll node, ll l, ll r, ll diff, ll s=1, ll e=n){
        propagate(node, s,e);
        if(l>r or r>n)return;
        if(e<l or r<s)return;
        if(l<=s and e<=r){
            tree[node].diff = (e-s+1)*diff;
            if(s==e)return;
            tree[node*2].lazy = diff;
            tree[node*2].chk=1;
            tree[node*2+1].lazy = diff;
            tree[node*2+1].chk=1;
            return;
        }
        ll mid = s+e>>1;
        upd(node*2,l,r,diff,s,mid);
        upd(node*2+1,l,r,diff,mid+1,e);
        tree[node].diff = tree[node*2].diff+tree[node*2+1].diff;
    }
    ll query(ll node, ll l, ll r, ll s=1, ll e=n){
        propagate(node,s,e);
        if(l>r or r>n)return 0;
        if(e<l or r<s)return 0;
        if(l<=s and e<=r)return tree[node].diff;
        ll mid = s+e>>1;
        return query(node*2,l,r,s,mid)+query(node*2+1,l,r,mid+1,e);
    }
} seg;
struct asdf2{
    ll l, r, w;
    bool operator < (const asdf2 o) const{
        if(l==o.l)return r<o.r;
        return l<o.l;
    };
};

int main(){
    fast;
    cin>>n>>m;
    vector<asdf2> v(n);
    for(auto &[a,b,c]:v)cin>>a>>b>>c;
    sort(all(v));
    ll s = v[0].r-v[0].l;
    vector<ll> v2(n);
    for(int i = 0 ; i < n ; i++)v2[i]=v[i].l;
    v2.push_back((ll)1e9+1);
    sort(all(v2));
    for(int i = 0 ; i < n ; i++){
        seg.upd(1,i+1,i+1,v[i].w);
    }
    while(m--){
        ll a,b; cin>>a>>b;
        ll l = upper_bound(all(v2), a-s)-v2.begin()+1;
        ll r = upper_bound(all(v2), a)-v2.begin();
        ll l2 = upper_bound(all(v2), b-s)-v2.begin()+1;
        ll r2 = upper_bound(all(v2), b)-v2.begin();
        ll l3 = max(l,l2);
        ll r3 = min(r,r2);
        ll ans = seg.query(1LL,l3,r3);
        cout<<ans<<"\n";
        seg.upd(1LL,l,r,0LL);
        seg.upd(1LL,l2,r2,0LL);
    }
}

 

124:06 ~ 142:53 : 4번 Subtask#1 해결 (S#1 : N≤15) +10점

 

3번을 S#4까지 풀고, S#5는 뭔가 못 풀것 같아서 일단 4번을 넘어왔다. (현재 248점)

이 문제는 그냥 지문을 읽어보자. 겁나 꼬아놔서 정리하기가 힘들다.

이 문제 또한 문제를 제대로 이해했다면, S#1은 그냥 브루트포스이다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
bool is_zigzag(vector<ll> &v){
    if(v.size()<=2)return 1;
    bool f = v[1]>v[0]; //f가 1이면 현재가 다음보다 작아야 함
    for(int i = 2 ; i < v.size() ; i++){
        if(f){
            if(v[i]>v[i-1])return 0;
        }
        else if(v[i]<v[i-1])return 0;
        f=!f;
    }
    return 1;
}
ll n;
int main(){
    fast;
    cin>>n;
    vector<ll> v(n);
    ll ans = 0;
    for(auto &i : v)cin>>i;
    for(int x = 1 ; x <= n ; x++){
        for(int y = 0 ; y < n ; y++){
            for(int z = y ; z < n ; z++){
                ll res = 0;
                for(int i = 0 ; i <= (1<<(z-y+1)) ; i++){
                    vector<ll> v2;
                    bool f=1;
                    for(int j = 0 ; j <= z-y ; j++){
                        if(i&(1<<j)){
                            if(v[y+j]>x){
                                f=0;
                                break;
                            }
                            v2.push_back(v[y+j]);
                        }
                    }
                    if(f and is_zigzag(v2))res = max(res, (ll)v2.size());
                }
                ans += res;
            }
        }
        cout<<ans<<"\n";
        ans=0;
    }
}

 

142:53 ~ 175:25 : 4번 Subtask#2 해결 (S#2 : N≤100) +13점

 

N이 100이다 <=> O(N^4)

dp[i][j][k] := (i,j) 안에서 현재 지그재그 상태(?)가 k일때, 가능한 최댓값

지그재그 상태는 대충 올라가는 지그재그인지, 내려가는 지그재그인지를 판단하는 거다.

음... 그냥 코드를 보자. (풀이를 까먹었다.) 간단한 dp이다. 근데 계속 값이 틀려서 시간을 꽤 날렸다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll dp[505][505][3];
ll x, n;
vector<ll> v;
ll f(ll i, ll j, ll k){
    ll &ret = dp[i][j][k];
    if(~ret)return ret;
    if(v[i]>x){
        if(i==j)return 0;
        return ret = f(i+1, j, k);
    }
    ret = 1;
    for(int l = i+1 ; l <= j ; l++){
        if(v[l]>x)continue;
        if(k==1){
            if(v[l]>v[i])continue;
        }
        else if(k==0){
            if(v[l]<v[i])continue;
        }
        if(k==2){
            if(v[l]<v[i])ret = max(ret, f(l,j,0)+1);
            else ret = max(ret, f(l,j,1)+1);
        }
        else ret = max(ret, f(l, j, !k)+1);
    }
    return ret;
}
int main(){
    fast;
    cin>>n;
    v.resize(n);
    for(auto &i : v)cin>>i;
    for(x = 1 ; x <= n ; x++){
        memset(dp, -1, sizeof(dp));
        ll ans = 0;
        for(int i = 0 ; i < n ; i++){
            for(int j = i ; j < n ; j++){
                ans += f(i,j,2);
            }
        }
        cout<<ans<<"\n";
    }
}

 

175:25 ~ 240:10 : 3번 해결 +52점

 

우리는 S#4 풀이를 확장할 것이다. 정확한 아이디어는 다음과 같다.

어떤 직선이 몇번 쿼리에 사라지는지를 빠르게 구할 수 있을까?

놀랍게도 가능하다! 이는 세그먼트 트리를 이용하면 된다. 먼저 직선과 모든 점들을 먼저 입력받자. 그리고 모든 좌표를 좌표압축시킨다.

우리는 어떤 점의 좌표가 pos일때, pos≤L_i, R_i<pos여야 삭제됨을 알고 있다. 또한 S#4에 의하여, 직선이 삭제되는 구간은 연속적임도 알고 있다. 이를 합치면, 각 직선이 어느 점에 의하여 가장 먼저 사라지는지를 알 수 있다!

세그먼트 트리에서 노드의 번호는 점의 위치, 노드의 값은 점의 쿼리 번호를 저장하게 할 것이다.

세그먼트 트리는 RMQ(구간 최솟값 쿼리)를 처리할 수 있도록 2개를 만들어둔다. 2개인 이유는 점 a, 점 b에 대한 세그트리를 따로 구성할 것이기 때문이다.

[L_i, R_i]를 차지하는 직선이 가장 먼저 삭제되는 쿼리를 어떻게 구해야 할까? 정답은 RMQ(L_i, R_i -1)이다. 왜냐?

1. L_i~R_i -1이 의미하는 것은, 이 직선에 포함되는 점들을 의미한다. (어떤 점의 위치가 pos이고 L_i≤pos<R_i이라면, 세그먼트 트리의 L_i~R_i -1번 사이 정점 어딘가에 위치하기 때문이다.)

2. 각 노드의 값들은 쿼리 번호를 의미하므로, RMQ(L_i, R_i -1)이 의미하는 것은, 이 직선에 포함되는 점들 중 쿼리가 가장 빠른 점의 쿼리 번호이다.

이를 a,b에 대하여 고려해준다. 그러면 둘 중 쿼리 번호가 작은 값의 쿼리에서 삭제된다고 저장하면 되고, a쿼리=b쿼리일 경우 2개의 점에 모두 포함된다는 의미이므로 weight를 더해주면 된다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll n, m;
vector<pair<ll,ll>> v;
struct segtree{
    vector<ll> tree;
    void init(){
        tree.resize(4040404, INT_MAX);
    }
    void upd(ll node, ll idx, ll diff, ll s=1, ll e=(n+m)*2){
        if(idx<s or e<idx)return;
        if(s==e){
            tree[node] = diff;
            return;
        }
        ll mid = s+e>>1;
        upd(node*2,idx,diff,s,mid);
        upd(node*2+1,idx,diff,mid+1,e);
        tree[node] = min(tree[node*2], tree[node*2+1]);
    }
    ll query(ll node, ll l, ll r, ll s=1, ll e=(n+m)*2){
        if(e<l or r<s)return INT_MAX;
        if(l<=s and e<=r)return tree[node];
        ll mid = s+e>>1;
        return min(query(node*2,l,r,s,mid),query(node*2+1,l,r,mid+1,e));
    }
} minseg, minseg2;
struct asdf2{
    ll l, r, w;
    bool operator < (const asdf2 o) const{
        if(l==o.l)return r<o.r;
        return l<o.l;
    };
};
pair<ll,ll> Q[252525];  //쿼리
vector<pair<ll,ll>> dline[1010101]; //dline[i] := i번 쿼리에서 삭제되는 직선들 번호
int main(){
    fast;
    minseg.init(); minseg2.init();
    cin>>n>>m;
    vector<asdf2> v(n);
    for(auto &[a,b,c]:v)cin>>a>>b>>c;
    sort(all(v));
    ll s = v[0].r-v[0].l;
    vector<ll> v2(n);
    for(int i = 0 ; i < n ; i++)v2[i]=v[i].l;
    v2.push_back((ll)1e9+1);
    sort(all(v2));
    vector<ll> e;
    for(int i = 0 ; i < m ; i++){
        cin>>Q[i].first>>Q[i].second;
        e.push_back(Q[i].first);
        e.push_back(Q[i].second);
    }
    for(int i = 0 ; i < n ; i++){
        e.push_back(v[i].l);
        e.push_back(v[i].r);
    }
    sort(all(e)); comp(e);
    for(int i = 0 ; i < m ; i++){
        ll a = lower_bound(all(e),Q[i].first)-e.begin()+1, b = lower_bound(all(e),Q[i].second)-e.begin()+1;
        if(minseg.query(1,a,a)>i)minseg.upd(1,a,i);
        if(minseg2.query(1,b,b)>i)minseg2.upd(1,b,i);
    }
    for(int i = 0 ; i < n ; i++){
        ll l = lower_bound(all(e), v[i].l)-e.begin()+1;
        ll r = lower_bound(all(e), v[i].r)-e.begin();   //원래 +1을 해야하지만, RMQ할때 어차피 r에는 1을 뺄거라서 안더함
        ll A = minseg.query(1,l,r), B = minseg2.query(1,l,r);
        if(A<INT_MAX or B<INT_MAX)dline[min(A,B)].push_back({i,(A==B)});
    }
    for(int i = 0 ; i < m ; i++){
        ll a,b; a=Q[i].first, b = Q[i].second;
        ll ans = 0;
        for(auto [j,k] : dline[i]){
            if(k)ans += v[j].w;
        }
        cout<<ans<<"\n";
    }
}

3번 문제는 플래티넘 중상위같으나(이게 가장 쉬운 풀이라면), 작년보단 쉬운 난이도같다. (solved.ac 티어 기준)

 

240:10 ~ 262:00 : 4번 Subtask#3 해결 (S#3 : N≤500) +17점

 

3번을 풀고 323점을 확보했으나, 340은 되야 금상에 안착할 수 있을 것 같았다. (물론 지금보니 커트라인은 243인가 그랬다.)

N≤500 <=> O(N^3)이다.

S#2에서 반복문 하나 줄이면 될것 같아서, 재귀를 최적화했더니 됐다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fast ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(v) v.begin(), v.end()
#define rll(v) v.rbegin(), v.rend()
#define comp(v) v.erase(unique(all(v)), v.end())
ll dp[505][505][3];
ll x, n;
vector<ll> v;
ll notless[505];
ll f(ll i, ll j, ll k){
    ll &ret = dp[i][j][k];
    if(~ret)return ret;
    if(v[i]>x){
        if(i==j)return 0;
        return ret = f(i+1, j, k);
    }
    ret = 1;
    if(i==j)return ret;
    if(notless[i]==-1 or notless[i]>j)return ret;
    ll next = notless[i];
    bool F=1;
    if(k==1){
        if(v[next]>v[i])F=0;
    }
    else if(k==0){
        if(v[next]<v[i])F=0;
    }
    if(k==2){
        if(v[next]<v[i])ret = max(ret, f(next,j,0)+1);
        else ret = max(ret, f(next,j,1)+1);
    }
    else if(F)ret = max(ret, f(next,j,!k)+1);
    ret = max(ret, f(next, j, k));
    return ret;
}
int main(){
    fast;
    cin>>n;
    v.resize(n);
    for(auto &i : v)cin>>i;
    for(x = 1 ; x <= n ; x++){
        memset(dp, -1, sizeof(dp));
        memset(notless, -1, sizeof(notless));
        ll ans = 0;
        for(int i = 0 ; i < n ; i++){
            for(int j = i+1 ; j < n ; j++){
                if(v[j]<=x){
                    notless[i]=j;
                    break;
                }
            }
        }
        for(int i = 0 ; i < n ; i++){
            for(int j = i ; j < n ; j++){
                ans += f(i,j,2);
            }
        }
        cout<<ans<<"\n";
    }
}

 

262:00~270:00 : 4번 S#4부터는 모르겠어서 걍 엎드려있었다. 4번은 풀질 못해서 티어는 모르겠다.

 

금상이 나왔으니 일단 만족한다. 작년에 동상이었는데(91점) 이정도면 꽤나 발전한 것 같다.

 

여담)3시간남았을때부터 화장실 가고싶었는데 흐름 끊길까봐 그냥 끝까지 참았다. 그냥 갈걸. 또한 4번 풀이가 부실한 이유는 이때 머릿속에 아무 생각도 없었기 때문이다(?)

'대회 > KOI' 카테고리의 다른 글

와 이걸 왜 대회 때 못풀었지  (0) 2024.07.06
KOI 2024 일기  (1) 2024.05.12
KOI 지역본선 2005  (0) 2023.10.09
KOI 지역본선 2004  (0) 2023.10.09