레이블이 수학인 게시물을 표시합니다. 모든 게시물 표시
레이블이 수학인 게시물을 표시합니다. 모든 게시물 표시

2018년 4월 6일 금요일

13997 이항 계수와 쿼리

13997 이항 계수와 쿼리 https://www.acmicpc.net/problem/13977

$M$개의 자연수 $N$과 정수 $K$가 주어질 때 $_{n}C_{r}$을 구하는 문제이다.

$O(N + log(p) + M)$에 해결할 수 있다.
$_{n}C_{r}$ = $n!/(r!*(n-r)!$ = $n!*(r!*(n-r)!)^{-1}$ = $n!*(r!)^{-1}*(n-r)!^{-1}$

$a^{p}$ $\equiv$ $a$ $(mod$ $p)$ ($a$는 자연수, $p$는 소수)
$a^{p - 1}$ $\equiv$ $1$ $(mod$ $p)$
$a^{p - 2}*a$ $\equiv$ $1$ $(mod$ $p)$ 즉, $a$의 역원은 $a^{p-2}$이다.
$a^{p-2}$는 분할정복으로 $log(p)$만에 구할 수 있고 역원들 또한 전처리해주어 $O(n)$에 해결할 수 있다.


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mod (ll)(1e9 + 7)
const int MAXN = 4e6 + 6;
int n, r;
ll fact[MAXN];
ll inverse[MAXN];
ll mpow(ll a, ll p) {
    if (p == 0return 1;
    if (p == 1return a;
    if (p & 1return mpow(a, p - 1)*a % mod;
    ll tmp = mpow(a, p / 2);
    return tmp * tmp % mod;
}
void init() {
    fact[0= fact[1= 1;
    for (int i = 2; i < MAXN; i++) fact[i] = fact[i - 1* i % mod;
    inverse[MAXN - 1= mpow(fact[MAXN - 1], mod - 2);
    for (int i = MAXN - 2; i >= 1; i--) inverse[i] = inverse[i + 1* (i + 1) % mod;
    inverse[0= inverse[1= 1;
}
ll nCr(ll n, ll r) {
    return ((fact[n] * inverse[r] % mod) * inverse[n - r]) % mod;
}
int main(){
    init();
    int t;
    scanf("%d"&t);
    while (t--) {
        scanf("%d%d"&n, &r);
        printf("%lld\n", nCr(n, r));
    }
    return 0;
}
cs

2018년 1월 2일 화요일

14609 구분구적법(Large)

14609 구분구적법(Large) https://www.acmicpc.net/problem/14609

$\int_{a}^{b}f(x)dx \approx \sum_{k=0}^{n-1}f(a+k \Delta x + \epsilon)\Delta x$
$\Delta x= (b-a)/n$
$0 \leq \epsilon \leq \Delta x$
$f(x)$, $a$,$b$, $n$이 주어질 때 근삿값이 적분값 $10^{-4}$이하의 오차를 만족하는 $\epsilon$을 구하는 문제다.

느낌적인 느낌으로 푼 문제다.
$\epsilon$이 어느 순간에 수렴하여 근삿값과 적분값이 일치할 때가 생길것 같았다.

삼분검색으로 해결했다.
이분탐색으로도 해결 가능하다.
근삿값이 적분값과 일치하지 않는$\epsilon$은 생기지 않는다.
즉, 위의 수식에 나온 범위내에서 만족하는 $\epsilon$은 항상 존재한다.

#include <cstdio>
#include <cmath>
using namespace std;
int N, M, a, b;
double s[11];
double x;
double sum = 0.0;
double isPossible(double mid) {
    double ret = 0.0;
    for (int m = 0;m < M;m++) {
        double tmp = 0.0;
        for (int n = N;n >= 0;n--) {
            tmp += s[n] * (pow(a + m*+ mid, n));
        }
        tmp *= x;
        ret += tmp;
    }
    return fabs(sum - ret);
}
int main() {
    scanf("%d"&N);
    for (int n = N;n >= 0;n--scanf("%lf"&s[n]);
    scanf("%d%d%d"&a, &b, &M);
 
    for (int n = N;n >= 0;n--) sum += s[n] * (pow(b, n + 1- pow(a, n + 1)) / (double)(n + 1);
 
    int cnt = 0;
    bool flag = false;
    x = (b - a) / (double)M;
    double l = 0.0, r = x;
    while (1) {
        double ll = (2 * l + r) / 3., rr = (l + 2 * r) / 3.;
        double g1 = isPossible(ll), g2 = isPossible(rr);
        if (g1 < g2) r = rr;
        else l = ll;
        if (++cnt == 10000) {
            if (fabs(g1 - g2) <= 1e-4) flag = true;
            break;
        }
    }
    if (flag) printf("%lf\n", l);
    else printf("-1\n");
    return 0;
}
cs

2017년 12월 27일 수요일

Codeforces #432 (Div.2) D - Arpa and a list of numbers

Codeforces #432 (Div.2) D - Arpa and a list of numbers
http://codeforces.com/contest/851/problem/D

수열이 주어진다.
각각의 수열값에 대하여 연산을 시행할 수 있다.
연산은 다음과 같이 두가지가 있다.
1. 해당 숫자를 없앤다. (cost x소모)
2. 숫자를 1 올린다. (cost y소모)
수열의 gcd를 1이 아니도록 만들때 소모되는 최소 cost를 구하는 문제이다.

당연히 대회때는 풀지 못했고 솔루션과 AC코드들을 참고해서 이해하는데도 한참걸렸다.

우선 수열의 값이 최대 $10^6$들어오므로 gcd를 2부터 $10^6$까지 올려보면서
수열들의 gcd를 현재 값으로 맞출때의 최소 cost를 구하면 된다.

최소 cost를 구하는 방법은 다음과 같다.
구간 [index + 1, index + gcd - 1]에 있는 $f$를 기준으로 [index + 1, $f$ - 1]에 포함되는 숫자들은 없애고 
[$f$, index + gcd - 1]에 포함되는 숫자들은 index + gcd로 만드는것이다.

이 $f$를 구하는 방법은 이분탐색으로 구해도 되지만 간단하게 gcd - x/y로 구할 수도 있다.

숫자들을 없애는 연산이나 1씩증가시키는 연산에 대해서는 구간합과 구간개수를 이용하면 된다.


#include <cstdio>
#define max(a,b) ((a)<(b)?(b):(a))
#define min(a,b) ((a)>(b)?(b):(a))
#define MAX 1005555
long long N, x, y;
long long sum[MAX + 1], cnt[MAX + 1];
long long gcnt(int r, int l) {
    if (r < l || l >= MAX) return 0;
    r = min(r, MAX - 1);
    return cnt[r] -  (l <= 0 ? 0 : cnt[l - 1]);
}
long long gsum(int r, int l) {
    if (r < l || l >= MAX) return 0;
    r = min(r, MAX - 1);
    return sum[r] - (l <= 0 ? 0 : sum[l - 1]);
}
int main() {
    scanf("%lld%lld%lld"&N, &x, &y);
    for (int n = 0, in;n < N;n++) {
        scanf("%d"&in);
        sum[in] += in;
        cnt[in]++;
    }
    for (int n = 1;n <= MAX;n++) {
        sum[n] += sum[n - 1];    
        cnt[n] += cnt[n - 1];
    }
    long long ans = N*x;
    for (int g = 2;g < MAX;g++) {
        int f = max(1, g - x / y);
        long long query = 0;
        for (int plus = 0; plus < MAX;plus += g) {
            // [plus + 1, plus + f - 1] delete
            query += gcnt(plus + f - 1, plus + 1* x;
            // [plus + f, plus + g - 1] plus
            query += (gcnt(plus + g - 1, plus + f)*((plus + g) * 1LL) - gsum(plus + g - 1, plus + f))*y;
        }
        ans = min(ans, query);
    }
    printf("%lld\n", ans);
    return 0;
}
cs

14672 윤호는 마법약 도둑

14672 윤호는 마법약 도둑 https://www.acmicpc.net/problem/14672

윤호는 M개의 N보다 작은 숫자가 적혀져있는 마법약들을 가지고 있다.
마법약은 1을 제외한 약수들중 하나로 추출 할 수 있다.
추출에 성공하려면 추출한 제품들 중에서 어떠한 원료도 공유해서는 안된다.
윤호의 추출할 수 있는 최대 마법약의 개수를 구하는 문제이다.

마법약들을 추출했을 때 모든 마법약들의 원료들이 서로소인 최대 마법약 개수를 구하는 것이다.
gcd를 이용하기에는 구할 방법이 마땅히 없어보였다.

사실상 원료들이 서로소가 되려면 마법약에서 추출할 수 원료들을 소수로 추출하면 된다.
문제는 최대 마법약 개수를 구해야하는데 이분매칭을 이용하면 된다.
네트워크를 나타내면 위와같이 될것이다.
N이 100,000,000이므로 최대 10,000까지의 소수만 구해주면 되는데 여기 실수가 있었다.

10,000보다 큰 수가 들어올 때 10,000보다 작은 소수로 소인수 분해를 하더라도 끝나지 않을 수 있다.
예를들어 104,729와 같은 수이다. 

이런 수는 따로 처리를 해서 저장을 해줘야한다.
밑의 코드에서 소인수 분해를 하고 x > 1인경우가 따로 처리한 경우이다.
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <queue>
using namespace std;
#define MAX 3500
#define PLUS 2300
#define INF 987654321
int N, M;
int arr[1001];
int pr[10011];
vector<int> prime;
int Size;
vector<int> adj[MAX];
int C[MAX][MAX], f[MAX][MAX];
const int src = MAX - 2, sink = MAX - 1;
int level[MAX], work[MAX];
unordered_map<intint> save;
int cnt = 0;
void init() {
    int L = (int)sqrt(N) + 1;
    for (int n = 2;n*<= L;n++) {
        for (int m = n*n;m <= L;m += n) pr[m] = true;
    }
    for (int n = 2;n <= L;n++if (!pr[n]) prime.push_back(n);
    Size = prime.size();
}
void add_edge(int u, int v, int c) {
    adj[u].push_back(v);
    adj[v].push_back(u);
    C[u][v] = c;
}
bool bfs() {
    memset(level, -1sizeof level);
    queue<int> q;
    q.push(src);
    level[src] = 0;
    while (!q.empty()) {
        int here = q.front();
        q.pop();
        for (auto &next : adj[here]) {
            if (level[next] == -1 && C[here][next] - f[here][next] > 0) {
                level[next] = level[here] + 1;
                q.push(next);
            }
        }
    }
    return level[sink] != -1;
}
int dfs(int here, const int sink, int flow) {
    if (here == sink) return flow;
    for (int &= work[here]; n < adj[here].size(); n++) {
        int next = adj[here][n];
        if (level[next] == level[here] + 1 && C[here][next] - f[here][next] > 0) {
            int get = dfs(next, sink, min(flow, C[here][next] - f[here][next]));
            if (get) {
                f[here][next] += get;
                f[next][here] -= get;
                return get;
            }
        }
    }
    return 0;
}
int main() {
    scanf("%d%d"&N, &M);
    init();
    for (int m = 0, x;m < M;m++) {
        scanf("%d"&x);
        for (int n = 0;n < Size;n++) {
            if (x % prime[n] == 0) {
                add_edge(n, m + PLUS, 1);
                while (x && x % prime[n] == 0) x /= prime[n];
            }
        }
        if (x > 1) {
            if (save[x] == 0) save[x] = ++cnt;
            add_edge(Size + save[x], m + PLUS, 1);
        }
        add_edge(m + PLUS, sink, 1);
    }
    for (int n = 0;n < Size;n++) add_edge(src, n, 1);
    for (int n = 1;n <= cnt;n++) add_edge(src, Size + n, 1);
    int ans = 0;
    while (bfs()) {
        memset(work, 0sizeof work);
        while (1) {
            int get = dfs(src, sink, INF);
            if (!get) break;
            ans += get;
        }
    }
    printf("%d\n", ans);
    return 0;
}
cs

2017년 12월 19일 화요일

11689 GCD(n, k) = 1

11689 GCD(n, k) = 1 https://www.acmicpc.net/problem/11689

GCD(n, k) = 1 을 만족하는 [1, n]범위 내에 있는 k의 개수를 구하는 문제다.

소인수 분해로 접근했다.
$45 = 3^{2}$*$5$이므로 3의배수 15개, 5의 배수 9개, 겹치는 개수 3개이므로 
45 - 15 - 9 + 3 = 24가 나온다.

하지만 소인수 분해했을 때 $3$*$5$*$7$*$11$*....이런식으로 간다면 겹치는 배수의 개수를 구하는것을
시간내에 해결할 수 없다.

사실 이 문제는 오일러 $\phi$(피 or 파이)함수를 구하는 문제다. (정의 자체가 문제와 동일)
수식을 보니까 정보 보안 강의에서 잠깐 배웠던게 생각이 났다.

오일러 $\phi$함수는 두 가지 성질이 있다.
1) $\phi(ab) = \phi(a)$*$\phi(b)$ ($a,b$는 서로소인 정수)
2) $\phi(p^{m}) = p^{m} - p^{m-1} = p^{m}$*$(1-1/p)$ ($p$는 소수, $m$은 양의 정수)

2)번식에서 $m$이 1이면 $\phi(p) = p-1$이 된다.

소인수 분해를 하고 위의 식을 적용하면 $O(\sqrt[]{N})$에 해결할 수 있다.
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll N;
ll mpow(ll x, ll y) {
    if (y == 0return 1LL;
    if (y == 1return x;
    if (y & 1return mpow(x, y - 1* x;
    ll get = mpow(x, y / 2);
    return get*get;
}
int main() {
    scanf("%lld"&N);
    ll ans = 1;
    for (ll n = 2;n*<= N;n++) {
        ll cnt = 0;
        while (N % n == 0) {
            N /= n;
            cnt++;
        }
        if (!cnt) continue;
        ans *= mpow(n, cnt) * (n - 1/ n;
    }
    printf("%lld\n", N == 1 ? ans : ans*(N - 1));
    return 0;
}
cs

2017년 9월 21일 목요일

Codeground 뗏목 레이스

Codeground 뗏목 레이스 https://www.codeground.org/practice/practiceProblemView
4225 쓰레기 슈트 https://www.acmicpc.net/problem/4225

다각형이 주어진다.
이 다각형을 어느 공간으로 넣을때 부딪치지 않고 통과할 수 있는 최소 너비를 구하는 문제다.

이 문제는 CCW를 이용해서 $O(N^3)$만에 풀 수 있다.
먼저 어느 한점과 또 다른 한점을 있는 직선을 생각해보자.
이 직선을 기준으로 Y축과 평행하게 도형을 놓는다면 다음과 같은 형태가 될것이다.
이 상태에서 도형의 너비는 직선을 기준으로 왼쪽에 점과 오른쪽에 있는 점의 직선과의 최대거리의 합이 되겠다.

여기서 직선의 왼쪽, 오른쪽을 판별하는게 CCW(Counter Clock Wise)가 되겠다.
사실 CCW개념을 모르고 풀었는데 한점과 직선과의 거리는 다음과 같이 나타낼 수 있다.
$|ax+by+c|/\sqrt{a^2+b^2}$ 
여기서 분자부분만 보면 이 값이 양수면 직선기준으로 반시계방향, 음수면 시계방향이라고한다.

처음에 잡은 두 점으로 $a, b$를 구하고 나머지 한점에 $x, y$를 대입해주면 그 값을 구할 수 있다.
최댓값들을 구해서 $\sqrt{a^2+b^2}$으로 나눠주고 그값들중 최솟값을 구하면 된다. 
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
struct Point{
   int x,y;
   Point(){}
   Point(int xx,int yy):x(xx),y(yy){}
   Point operator-(Point const &other){
      return Point(x-other.x,y-other.y);
   }
};
int N;
Point p[101];
int main(){
   int cnt = 1;
   while(scanf("%d",&N), N != 0){
      for(int n=0;n<N;n++scanf("%d%d",&p[n].x,&p[n].y);
 
      double ans = 1e13;
      for(int n=0;n<N;n++){
         for(int m=n+1;m<N;m++){
            int a,b,c;
            a = p[m].y - p[n].y;
            b = p[n].x - p[m].x;
            c = p[n].y*(p[m].x-p[n].x) - p[n].x*(p[m].y-p[n].y);
 
            double Min = 1e10, Max = -1e10;
            for(int k=0;k<N;k++){
               double d = a*p[k].x + b*p[k].y + c;
               if(d < 0) Min = min(Min, d);
               else if(d > 0) Max = max(Max, d);
            }
            double get = 0.0;
            if(Min < 0) get -= Min;
            if(Max > 0) get += Max;
            get /= sqrt(a*a+b*b);
            ans = min(ans, get);
         }
      }
      printf("Case %d: %.2lf\n",cnt++,ans + 0.005);
   }
}
 
cs