Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

별사탕와쟉와쟉

ICPC Seoul Regional 2020 풀어보기 - 2 본문

알고리즘

ICPC Seoul Regional 2020 풀어보기 - 2

ConfeitoHS 2022. 1. 28. 16:59

총 12문제 중, 난이도 순서대로 H, I, L, D, F, K를 풀어보자.


H - Needle

벽이 1 간격으로 세 레이어가 있는데, 각 벽에는 구멍이 나있다고 한다. 각 벽의 구멍 위치가 주어졌을 때, 바늘이 지나갈 수 있는 경우의 수를 구하라는 문제다.

 

구멍의 개수는 한 줄당 최대 5만개, 위치값은 -30000부터 30000의 정수이다. 전체탐색하면 위쪽 벽 50000개, 아래쪽 벽 50000개를 조합해 평균값을 내고 그 값을 가지는 중간 벽 구멍이 있으면 풀 수 있지만 50000*50000 = 2.5x10^9이므로 시간 초과가 난다. n^2이 아닌 nlogn으로 풀어야 한다.

 

키포인트

위쪽 벽과 아래쪽 벽 위치 값을 평균낸다는 아이디어를 재활용하자. 총 60001개의 위치가 가능하므로 구멍의 위치는 60001차원 벡터로 표현 가능하다. 그리고 중간 벽의 n번째 구멍을 지날 수 있기 위해선 위쪽 벽에 (n-k), 아래쪽 벽에 n+k 위치에 구멍이 있어야 한다. 즉 더해서 2n이 되어야한다.

이를 각 계수가 구멍의 존재 유무(1 또는 0인) 60000차 다항식 곱셈으로 쉽게 표현할 수 있다. 중간 벽 5 위치에 구멍이 뚫려있다고 하자. 이를 확인하기 위해서 위아래 벽 다항식을 곱하면 5차*5차 + 4차*6차 + 3차*7차가 되고 구멍 유무가 저절로 곱해진다. 이를 nlogn으로 푸는 방법인 FFT를 사용할 수 있다. 보통 쿨리-튜키 알고리즘을 쓴다.

 

주의할 점이 한가지 있는데, 위치가 음수일 수 있으므로 배열에 입력받을 때 65536x2짜리 배열의 인덱스 30000을 0으로 하자. FFT를 진행하면 두 방정식이 곱해지므로 60000이 0의 위치가 된다. iDFT 결과 위치에 30000을 빼면 원래대로 30000을 0점으로 사용할 수 있다.

 

코드

더보기
#include<bits/stdc++.h>
#define lld long long
using namespace std;

typedef complex<double> cxd;

int off = 30000;
const double PI = 3.14159265358979323846;
void FFT(vector<cxd> &a, bool inv){

    //FFT Code by https://aruz.tistory.com/1

    int n = a.size();
    if(n==1) return;

    vector<cxd> ev(n/2), od(n/2);
    for(int i=0 ; 2*i<n ; i++){
        ev[i] = a[2*i];
        od[i] = a[2*i+1];
    }
    FFT(ev,inv);
    FFT(od,inv);

    double euler = 2 * PI/n * (inv?-1:1);
    cxd w(1), wn(cos(euler),sin(euler));

    for(int i=0 ; 2*i<n ; i++, w*=wn){
        a[i] = ev[i]+w*od[i];
        a[n/2+i] = ev[i]-w*od[i];
    }

    if(inv) for(cxd &e : a) e/=2;    // divide by N = 2^k 
}

vector<cxd> up(65536*2);
vector<int> md(65536);
vector<cxd> dn(65536*2);
int U,M,D;

int main(){
    ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
    cin >> U;
    for(int i=0;i<U;i++){
        int t;
        cin >> t;
        up[t+off]={1,0};
    }
    cin >> M;
    for(int i=0;i<M;i++){
        int t;
        cin >> t;
        md[t+off]=1;
    }
    cin >> D;
    for(int i=0;i<D;i++){
        int t;
        cin >> t;
        dn[t+off]={1,0};
    }
    
    
    vector<int> total;
    FFT(up,0);
    
    FFT(dn,0);
    for(int i=0;i<65536*2;i++){
        up[i]*=dn[i];
    }
    FFT(up,1);
    int count = 0;
    for(int i=0;i<65536*2;i++){
        int real = round(up[i].real());
        if(i%2==0){
            if(md[i/2]==1) count+=real;
        }
    }
    cout << count;
    //code
}

'알고리즘' 카테고리의 다른 글

ICPC Seoul Regional 2020 풀어보기 - 1  (0) 2022.01.18