페이지

레이블이 convex hull인 게시물을 표시합니다. 모든 게시물 표시
레이블이 convex hull인 게시물을 표시합니다. 모든 게시물 표시

12736번: Fireworks

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

i번 연결점에 이어진 폭약들을 시각 t에 동시에 떠뜨리기 위한 최소 비용을 f_i(t)라고 하자. 이 함수는 t에 관한 아래로 볼록인 함수이다.
또한 이 함수는 i번 연결점 바로 다음에 이어진 a0, a1, ..., aj 번 연결점에 관한 최소 비용 함수로부터 구할 수 있다.
(그리 어렵지 않으니 함수가 어떻게 구해지는지 한 번 생각보자.)

이 과정에서 함수끼리의 덧셈 및 볼록껍질의 제약적인 수정이 요구된다.
볼록 함수 끼리의 연산을 효율적으로 하기 위해 볼록 함수를 y절편, 기울기가 1씩 증가하는 t를 우선순위 큐에 저장해놓는다.
- 두 함수를 더하기 위해서는 y절편끼리 더하고 사이즈가 큰 우선순위 큐에 작은 우선순위 큐의 원소들을 모두 빼서 넣어주면 된다. ... (*)
- 볼록 껍질에서 기울기가 1보다 큰 부분을 없애야 하는 경우가 있다. 이 경우엔 간단히 우선순위 큐에서 pop 해주면 된다.
- 볼록 껍질에서 기울기가 -1인 부분을 늘려야 하는 경우가 있다. 이 경우엔 y절편과 기울기가 0인 부분을 잘 수정해주면 된다.

(*) 부분의 시간복잡도를 계산해보자.
먼저, 각 f_i(t)를 이루는 점의 수(곧, 우선순위 큐의 원소 개수)는 i번 연결점에 연결된 폭약의 수에 비례한다. 우선순위 큐 내부의 폭약에 관한 원소들은 (*) 부분에서 이동이 발생하고 그 때마다 자신이 속한 우선순위 큐의 사이즈가 2배이상으로 증가한다. 따라서 각 원소마다 이동횟수가 $O(\lg n)$로 제한되고 이동시 $O(\lg n)$의 시간이 걸리므로 총 시간복잡도는 $O(n\lg^2 n)$.

최종 시간복잡도는 $O(n\lg^2 n)$

#include<cstdio>
#include<queue>
using namespace std;
int n, m, p[300001], c[300001], sz[300001];
long long s[300001], l, r;
priority_queue<long long> *pq[300001];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 2; i <= n + m; i++) scanf("%d%d", p + i, c + i);
    for (int i = n + 1; i <= n + m; i++) pq[i] = new priority_queue<long long>(2, 0);
    for (int i = n + m; i; i--) {
        while (sz[i]--) pq[i]->pop();
        r = pq[i]->top(); pq[i]->pop();
        l = pq[i]->top(); pq[i]->pop();
        pq[i]->push(l + c[i]);
        pq[i]->push(r + c[i]);
        s[p[i]] += s[i] + c[i];
        if (!pq[p[i]]) { pq[p[i]] = pq[i]; continue; }
        if (pq[p[i]]->size() < pq[i]->size()) swap(pq[p[i]], pq[i]);
        while (!pq[i]->empty()) pq[p[i]]->push(pq[i]->top()), pq[i]->pop();
        sz[p[i]]++;
    }
    pq[0]->pop();
    while (!pq[0]->empty()) s[0] -= pq[0]->top(), pq[0]->pop();
    printf("%lld", s[0]);
    return 0;
}

1708번: 볼록 껍질

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

graham scan을 이용한다.

시간복잡도는 $O(n\lg n)$

#include<cstdio>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef pair<intint> point;
long long ccw(point i, point j, point k) {
    return 1LL * (j.x - i.x)*(k.y - i.y) - 1LL * (k.x - i.x)*(j.y - i.y);
}
int n, sz;
point p[100000], stk[100000];
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d%d", &p[i].x, &p[i].y);
    swap(p[0], *min_element(p, p + n));
    sort(p + 1, p + n, [](point i, point j) {
        long long t = ccw(p[0], i, j);
        return t > 0 || !t&&i < j;
    });
    for (int i = 0; i < n; i++) {
        while (sz > 1 && ccw(stk[sz - 2], stk[sz - 1], p[i]) <= 0) sz--;
        stk[sz++] = p[i];
    }
    printf("%d", sz);
    return 0;
}



위, 아래 껍질을 나눠서 구할 수도 있다.

시간복잡도는 $O(n\lg n)$

#include<cstdio>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef pair<intint> point;
point p[100000], low[100000], up[100000];
int n, sl, su;
long long ccw(point i, point j, point k) {
    return 1LL * (j.x - i.x)*(k.y - i.y) - 1LL * (k.x - i.x)*(j.y - i.y);
}
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d%d", &p[i].x, &p[i].y);
    sort(p, p + n);
    for (int i = 0; i < n; i++) {
        while (sl > 1 && ccw(low[sl - 2], low[sl - 1], p[i]) >= 0) sl--;
        while (su > 1 && ccw(up[su - 2], up[su - 1], p[i]) <= 0) su--;
        low[sl++] = up[su++] = p[i];
    }
    printf("%d", sl + su - 2);
    return 0;
}

9240번: Robert Hood

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

$O(n\lg n)$

#include<cstdio>
#include<algorithm>
#define x first
#define y second
#define dis(a,b) hypot(a.x-b.x,a.y-b.y)
using namespace std;
const int MXN = 1e5;
int t, n;
typedef struct pair<intint> point;
point p[MXN], ch[MXN];
int ccw(point a, point b, point c) {
    return (b.x - a.x)*(c.y - a.y) - (c.x - a.x)*(b.y - a.y);
}
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d %d", &p[i].x, &p[i].y);
    swap(p[0], *min_element(p, p + n));
    sort(p + 1, p + n, [](point l, point r) {
        int c = ccw(p[0], l, r);
        return c > 0 || !c && l < r;
    });
    int sz = 0;
    for (int i = 0; i < n; i++) {
        while (sz > 1 && ccw(ch[sz - 2], ch[sz - 1], p[i]) <= 0) sz--;
        ch[sz++] = p[i];
    }
    double maxi = 0;
    for (int i = 0, j = 1; i < sz; i++) {
        while (ccw(ch[i], ch[(i + 1) % sz], { ch[i].x + ch[(j + 1) % sz].x - ch[j].x, ch[i].y + ch[(j + 1) % sz].y - ch[j].y }) > 0) j = (j + 1) % sz;
        if (maxi < dis(ch[i], ch[j])) maxi = dis(ch[i], ch[j]);
    }
    printf("%lf", maxi);
    return 0;
}

10254번: Highway

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

$O(tn\lg n)$

#include<cstdio>
#include<algorithm>
#define x first
#define y second
#define dis(a,b) 1LL*(a.x-b.x)*(a.x-b.x)+1LL*(a.y-b.y)*(a.y-b.y)
using namespace std;
const int MXN = 2e5;
int t, n;
typedef struct pair<intint> point;
point p[MXN], ch[MXN], ra, rb;
long long ccw(point a, point b, point c) {
    return 1LL * (b.x - a.x)*(c.y - a.y) - 1LL * (c.x - a.x)*(b.y - a.y);
}
void f() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d %d", &p[i].x, &p[i].y);
    swap(p[0], *min_element(p, p + n));
    sort(p + 1, p + n, [](point l, point r) {
        long long c = ccw(p[0], l, r);
        return c > 0 || !c && l<r;
    });
    int sz = 0;
    for (int i = 0; i < n; i++) {
        while (sz > 1 && ccw(ch[sz - 2], ch[sz - 1], p[i]) <= 0) sz--;
        ch[sz++] = p[i];
    }
    long long maxi = 0;
    for (int i = 0, j = 1; i < sz; i++) {
        while (ccw(ch[i], ch[(i + 1) % sz], { ch[i].x + ch[(j + 1) % sz].x - ch[j].x, ch[i].y + ch[(j + 1) % sz].y - ch[j].y }) > 0) j = (j + 1) % sz;
        if (maxi < dis(ch[i], ch[j])) {
            maxi = dis(ch[i], ch[j]);
            ra = ch[i];
            rb = ch[j];
        }
    }
    printf("%d %d %d %d\n", ra.x, ra.y, rb.x, rb.y);
}
int main() {
    for (scanf("%d", &t); t--;) f();
    return 0;
}

14181번: 함수와 쿼리

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

$O(n\lg n+m(\lg n)^2)$

f(x,y)를 쉽게 생각해보자.
a[y]
a[y] a[y-1]
a[y] a[y-1] a[y-2]
...
a[y] a[y-1] a[y-2] ... a[y-x+2] a[y-x+1]
이런 삼각형 모양의 배열의 맨 위에 포인터 p가 있다하자.
p는 바로 아래 혹은 아래 오른쪽으로 움직인다.
맨 위에서 아래까지 움직일 때 p가 지나간 항들 합의 최소가 f(x,y)이다.

편의상 t=y-x+1, s[i]=a[1]+a[2]+...+a[i]라 하자.
잘 생각해보면 대각선으로만 움직이다 아래로만 내려가는 경로 중에 최적루트가 있다.
이때, 도착지점이 a[i]라 하면 a[i+1...y]>a[i]이다. ... (*)
즉, f(x,y)=min(t<=i<=t)(s[y]-s[i]+a[i]*(i-t+1))
min 안을 정리해서 다시 쓰면
-a[i]*t+(i+1)*a[i]-s[i]+s[y]
(*)에 의해 [t,y]에 있고 -a[i]가 단조 감소인 i에 대해 (기울기, y절편)=(-a[i],(i+1)*a[i]-s[i]) 직선들을 생각할 수 있다.
이들을 이용해 최솟값을 이루는 convex hull을 만들어 x=t인 지점의 함수값을 구하면 f(x,y)를 구할 수 있다.

다수의 쿼리 빠르게 처리하기 위해 세그먼트 트리를 이용한다.
트리의 [l,r] 구간에 해당하는 convex hull을 미리 만들어 놓고 쿼리 (a,b)가 들어오면 이에 해당하는 구간들의 x=a에서 함수값 중 최솟값을 출력한다.

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MXN = 1e5;
typedef pair<doubledouble> line;
int n, m, sz[MXN * 4], a[MXN + 1], s[MXN + 1];
vector<line> v[MXN * 4];
vector<double> x[MXN * 4];
double cross(line i, line j) { return (i.second - j.second) / (j.first - i.first); }
void add(int h, int l, int r, int g, line t) {
    if (r < g || g < l) return;
    if (l^r) {
        add(h * 2 + 1, l, (l + r) / 2, g, t);
        add(h * 2 + 2, (l + r) / 2 + 1, r, g, t);
    }
    v[h].resize(r - l + 1);
    x[h].resize(r - l + 1);
    while (sz[h] && v[h][sz[h] - 1].first <= t.first || sz[h] > 1 && cross(v[h][sz[h] - 2], t)>cross(v[h][sz[h] - 1], t)) sz[h]--;
    if (sz[h]) x[h][sz[h] - 1] = cross(v[h][sz[h] - 1], t);
    v[h][sz[h]++] = t;
}
int query(int h, int l, int r, int gl, int gr) {
    if (r < gl || gr < l) return 1e9;
    if (gl <= l&&r <= gr) {
        int p = lower_bound(x[h].begin(), x[h].begin() + sz[h] - 1, gl) - x[h].begin();
        return v[h][p].first*gl + v[h][p].second;
    }
    return min(query(h * 2 + 1, l, (l + r) / 2, gl, gr), query(h * 2 + 2, (l + r) / 2 + 1, r, gl, gr));
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        s[i] = s[i - 1] + a[i];
        add(0, 1, n, i, { -a[i],(i + 1)*a[i] - s[i] });
    }
    scanf("%d", &m);
    for (int i = 0, u, v; i < m; i++) {
        scanf("%d%d", &u, &v);
        printf("%d\n", query(0, 1, n, v - u + 1, v) + s[v]);
    }
    return 0;
}

14180번: 배열의 특징

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


$O(n\lg n)$

a[i]를 j번째로 옮길 때
i) j<=i
c는 s[i-1]-s[j-1]-(i-j)*a[i] 만큼 증가
정리하면
j*a[i]-s[j-1]+s[i-1]-i*a[i]
(기울기, y절편)=(j, -s[j-1]) 직선들을 가지고 convex hull trick을 써서 해결
a[i]는 i에 대한 단조 함수가 아니므로 직선들의 교점을 가지고 이분 탐색을 해야한다.

ii) j>=i
c는 -s[j]+s[i]+(j-i)*a[i] 만큼 증가
정리하면
j*a[i]-s[j]+s[i]-i*a[i]
(기울기, y절편)=(j, -s[j]) 직선들을 가지고 convex hull trick을 써서 해결
마찬가지로 이분 탐색

#include<cstdio>
#include<algorithm>
using namespace std;
const int MXN = 2e5;
typedef long long ll;
typedef pair<doubledouble> line;
int n, sz;
ll a[MXN + 1], s[MXN + 1], maxi, c;
line l[MXN];
double x[MXN - 1];
double cross(line i, line j) { return (i.second - j.second) / (j.first - i.first); }
void push(line h) {
    while (sz > 1 && cross(l[sz - 2], h) > cross(l[sz - 1], h)) sz--;
    if (sz) x[sz - 1] = cross(l[sz - 1], h);
    l[sz++] = h;
}
ll query(double h) {
    int p = lower_bound(x, x + sz - 1, h) - x;
    return l[p].first*h + l[p].second;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
        s[i] = s[i - 1] + a[i];
        c += i*a[i];
    }
    for (int i = 1; i <= n; i++) {
        push({ i, -s[i - 1] });
        maxi = max(maxi, query(a[i]) + s[i - 1] - i*a[i]);
    }
    sz = 0;
    for (int i = n; i; i--) {
        push({ -i, -s[i] });
        maxi = max(maxi, query(-a[i]) + s[i] - i*a[i]);
    }
    printf("%lld", c + maxi);
    return 0;
}





$O(n)$

a[j]를 i번째로 옮길때
i) i<=j
(-a[j])*(-i)+s[j-1]-j*a[j]-s[i-1] 만큼 증가한다.
(기울기, y절편)=(-a[j], s[j-1]-j*a[j]) 직선들을 생각해보면 convex hull trick을 쓰기 위해서 -a[j]가 j에 대해 단조 증가 함수일 필요가 있다.
실제로 잘 생각해보면 -a[j]가 증가할 때만 직선을 추가해도 상관 없다.
결과적으로 위 풀이와는 다르게 -i 또한 단조 증가하므로 O(n)에 해결 가능.

ii) i>=j
a[j]*i-s[i]+s[j]-j*a[j] 만큼 증가한다.
마찬가지로
(기울기, y절편)=(a[j], s[j]-j*a[j]) 직선들 중 a[j]가 증가할 때만 직선을 추가해서 convex hull trick을 적용한다.

#include<cstdio>
#include<algorithm>
using namespace std;
const int MXN = 2e5;
typedef long long ll;
typedef pair<doubledouble> line;
int n, p, sz;
long long a[MXN + 1], s[MXN + 1], c, t;
line l[MXN];
double cross(line i, line j) { return (i.second - j.second) / (j.first - i.first); }
void push(line x) {
    while (sz - p > 1 && cross(l[sz - 2], x) > cross(l[sz - 1], x)) sz--;
    l[sz++] = x;
}
ll query(int x) {
    while (sz - p > 1 && cross(l[p], l[p + 1]) < x) p++;
    return l[p].first*x + l[p].second;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
        s[i] = s[i - 1] + a[i];
        c += i*a[i];
    }
    int maxi = -1e9, mini = 1e9;
    for (int i = n; i; i--) {
        if (mini>a[i]) {
            mini = a[i];
            push({ -a[i],s[i - 1] - i*a[i] });
        }
        t = max(t, query(-i) - s[i - 1]);
    }
    p = sz = 0;
    for (int i = 1; i <= n; i++) {
        if (maxi < a[i]) {
            maxi = a[i];
            push({ a[i],s[i] - i*a[i] });
        }
        t = max(t, query(i) - s[i]);
    }
    printf("%lld", c + t);
    return 0;
}

13421번: 국민 랜드

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


"국민 랜드는 한 변의 길이가 1 이상인 정사각형......


$O(1)$

정사각형 길이가 2s 이라면 네 꼭지점 좌표는 (-s,-s), (-s,s), (s,-s), (s,s)가 된다.
기존의 네 점 (ai,bi)를 위 네 점과 대응시키는 4!의 경우가 존재한다.
모든 경우에 대해 가장 작은 비용의 최대 크기 정사각형을 찾자.
대응 관계가 정해졌을 때 s관한 비용의 함수 f(s)는

|-s-a1|+|-s-b1|+
|-s-a2|+|s-b2|+
|s-a3|+|-s-b3|+
|s-a4|+|s-b4|
가 되며 s=(A={-a1,-b1,-a2,b2,a3,-b3,a4,b4}의 중앙값) 일 때 함숫값이 최소가 된다.
최소 비용 중 최대 크기 정사각형을 구해야 하므로 정렬한 A의 원소 중 5번째 값을 s의 후보로 놓는다.
s가 음수가 될 수도 있는데 -s도 존재하므로 최소 비용 상의 최대 s값을 취하면 상관없다.
답은 2s이다.

네 점이 모두 원점에 있을 경우 s=0이 나올 것이다. 이 때는 문제 조건에 따라 1을 출력한다.


#include<cstdio>
#include<algorithm>
using namespace std;
pair<intint> p[4];
long long mini = 8e9;
int r, a[8];
int main() {
    for (int i = 0; i<4; i++) scanf("%d%d", &p[i].first, &p[i].second);
    sort(p, p + 4);
    do {
        for (int i = 0; i < 4; i++)
            a[i] = p[i].first*(1 - i / 2 % 2 * 2),
            a[i + 4] = p[i].second*(1 - i % 2 * 2);
        sort(a, a + 8);
        long long s = 0;
        for (int i = 0; i < 8; i++) s += abs(a[i] - a[4]);
        if (s<mini || s == mini&&a[4]>r) mini = s, r = a[4];
    } while (next_permutation(p, p + 4));
    printf("%d", max(r * 2, 1));
    return 0;
}

1866번: 택배

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


$O(n^2)$


각 지점을 시작 지점까지의 거리를 기준으로 오름차순 정렬한다.(=ai)
dp[i]: 1~i번째 지점에 물품을 배송하는데 필요한 최소 비용
dp[i]는 1<=j<i인 모든 j에 대해

(1) 1~j-1 지역에 배송하는데 드는 최소 비용 즉, dp[j-1]
+
(2) j~i 어느 한 지역에 헬리콥터를 이용하여 j-i+1개의 물품을 배송받은 뒤 j~i 지역 각각 하나씩 물품을 배송하는 비용
의 최솟값을 취하면 된다.

(2) 비용을 최소화하기 위해 j~i 지역의 중앙에 위치한 지역에서 헬리콥터로 물품을 배송받아야 한다.
이는 |aj-x| + ... + |ai-x| 의 최솟값이 x=a(i+j)/2일 때이기 때문이다.

이제 j=i -> 1 까지 옮겨가며 aj...ai 의 중앙값을 하나의 변수를 통해 유지해준다면 쉽게 dp를 할 수 있을 것이다.


#include<cstdio>
#include<algorithm>
using namespace std;
int n, a[3001], x, y, dp[3001];
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", a + i);
    sort(a + 1, a + 1 + n);
    scanf("%d%d", &x, &y);
    for (int i = 1; i <= n; i++) {
        dp[i] = a[i] * x + dp[i - 1];
        for (int j = i, t = y; j; j--) {
            t += (a[i + j + 1 >> 1] - a[j])*x;
            dp[i] = min(dp[i], dp[j - 1] + t);
        }
    }
    printf("%d", dp[n]);
    return 0;
}

1027번: 고층 건물

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


$O(n^2)$

i번 건물에서 j(j>i)번 건물을 보고 번호를 1씩 증가시킨다 하자.
두 건물을 이은 직선의 기울기가 지금까지의 것보다 큰 경우에만 두 건물 사이에서 서로를 볼 수 있다.


#include<cstdio>
#include<algorithm>
int n, a[50], b[50];
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", a + i);
    for (int i = 0; i < n; i++) {
        double t = -9e9, m;
        for (int j = i + 1; j < n; j++) {
            m = (double)(a[j] - a[i]) / (j - i);
            if (m > t) t = m, b[i]++, b[j]++;
        }
    }
    printf("%d", *std::max_element(b, b + n));
    return 0;
}

2821번: 컨베이어 벨트

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


볼록 껍질, 동적계획법


$O(n+m\lg n)$

si=sum(ti)라 하자.
자동차 fj와 fj+1 사이에 상근이가 쉬어야 할 시간은
임의의 i에 대해 d+si*fj+1>=si+1*fj 를 만족하는 최소 d 값이 된다.
이를 이용해서 주어진 j 마다 d를 빠르게 구하면 이 문제를 해결할 수 있다.
위 식을 다시 써보자.
d>= si+1*fj - si*fj+1
우변식은 Pi(si+1,si), Qi(fj, -fj+1) 두 점의 내적 결과라는 것을 알 수 있다.
이것의 의미(=d)는 원점과 Qi를 연결한 직선 위에 Pi를 정사영한 점과 원점까지의 거리로 해석할 수 있다.
Pi는 1사분면 위의 점이고 Qi는 4사분면 위의 점이다. Pi 점을 아무렇게 몇 개 찍어놓자.
또한 Pi들을 모두 포함하는 아래로 볼록한 볼록 껍질을 그려보자.
직관적으로 최소 d를 구하기 위해서는 주어진 Qi와 원점까지의 직선의 법선이 볼록 껍질과 접할 때의 그 pi를 이용하면 된다는 것을 알 수 있다.
이를 구하기 위해서는 법선의 기울기를 이용해 주어진 볼록껍질들을 이루는 선분의 기울기(오름차순)들 안에서 이진탐색을 이용해 최적화시키는 정점을 구하면 된다.


#include<stdio.h>
#include<algorithm>
#define x first
#define y second
using namespace std;
const int MAX_N = 1e5, MAX_M = 1e5;
typedef long long ll;
int n, m, t[MAX_N], f[MAX_M], sz;
pair<llll> p[MAX_N + 1];
double a[MAX_N];
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++) scanf("%d", t + i);
    for (int i = 0; i < m; i++) scanf("%d", f + i);
    p[sz++] = { 0,0 };
    p[sz++] = { t[0],0 };
    ll s = t[0];
    for (int i = 1; i < n; i++) {
        while ((p[sz - 1].y - p[sz - 2].y)*(s + t[i] - p[sz - 1].x)>(s - p[sz - 1].y)*(p[sz - 1].x - p[sz - 2].x)) sz--;
        p[sz++] = { s + t[i],s };
        s += t[i];
    }
    ll res = s*f[m - 1];
    for (int i = 0; i < sz - 1; i++) a[i] = (double)(p[i + 1].y - p[i].y) / (p[i + 1].x - p[i].x);
    for (int i = 0, h; i < m - 1; i++) {
        h = upper_bound(a, a + sz - 1, (double)f[i] / f[i + 1]) - a;
        res += p[h].x*f[i] - p[h].y*f[i + 1];
    }
    printf("%lld", res);
    return 0;
}