페이지

레이블이 prefix sum인 게시물을 표시합니다. 모든 게시물 표시
레이블이 prefix sum인 게시물을 표시합니다. 모든 게시물 표시

7332번: Cashier Employment

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

각 시간대마다 필요한 알바 수와 8시간동안 연속적으로 고용할 수 있는 알바 정보가 주어졌을 때 필요한 알바 수의 최솟값을 구하는 문제이다.

r[i]: i-1 ~ i시에 필요한 알바 수
t[i]: i-1 ~ i+7시에 쓸 수 있는 알바 수
* 굳이 i-1로 한 이유는 후에 prefix sum을 사용하는데 있어 -1번째 인덱스가 나오지 않게 하기 위함이다.

그러면 문제는 0<= a[i] <= t[i]인 a[i]를 잘 골라
i<8 일 때, a[17+i] + ... + a[24] + a[1] + ... + a[i] >= r[i]
i>=8 일 때, a[i-7] + a[i-6] + ... + a[i] >= r[i]
를 만족하는 sum(a[i])의 최솟값을 구하는 문제라고 할 수 있다.

s[i] = sum(a[j]: 1<=j<=i)로 놓고 모든 조건을 일차선형부등식 꼴로 만들어 보면
0 <= s[i] - s[i-1] <= t[i]
i<8 일 때, s[24] - s[16+i] + s[i] >= r[i]
i>=8 일 때, s[i] - s[i-8] >= r[i]

이제 상수 lmt <= s[24]로 놓으면
s[24] - lmt >= s[0]
s[i] >= s[i-1]
s[i-1] + t[i] >= s[i]
i<8 일 때, s[i] + lmt - r[i] >= s[16+i]
i>=8 일 때, s[i] - r[i] >= s[i-8]

모든 부등식이 x + w >= y 꼴의 형태이므로 각 조건에 대응되는 가중치가 w인 x -> y 간선들을 추가한 뒤, 최단거리 알고리즘을 이용해 주어진 부등식들이 가능한지 판단할 수 있다.
부등식들이 모두 성립하는 최소 0 <= lmt <= n를 찾는다. 불가능하면 No Solution을 출력한다.

시간복잡도는 테스트 케이스마다 $O(n)$

#include<cstdio>
#include<algorithm>
using namespace std;
int tc, n, t[25], r[25], dp[25][25];
bool f(int lmt) {
    fill(dp[0], dp[25], 1e9);
    for (int i = 0; i < 25; i++) {
        dp[i][i] = 0;
        if (i) dp[i - 1][i] = t[i], dp[i][i - 1] = 0;
    }
    int i = 1;
    for (; i < 8; i++) dp[i][i + 16] = lmt - r[i];
    for (; i < 25; i++) dp[i][i - 8] = -r[i];
    dp[24][0] = -lmt;
    for (int i = 0; i < 25; i++) for (int j = 0; j < 25; j++) for (int k = 0; k < 25; k++) dp[j][k] = min(dp[j][k], dp[j][i] + dp[i][k]);
    for (int i = 0; i < 25; i++) if (dp[i][i] < 0) return false;
    return true;
}
void task() {
    for (int i = 1; i <= 24; i++) scanf("%d", r + i), t[i] = 0;
    scanf("%d", &n);
    for (int i = 0, x; i < n; i++) scanf("%d", &x), t[x + 1]++;
    for (int i = 0; i <= n; i++) if (f(i)) { printf("%d\n", i); return; }
    puts("No Solution");
}
int main() {
    for (scanf("%d", &tc); tc--;) task();
    return 0;
}

4011번: 기름 파기

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

$O(nm)$

격자를 한 구역당 하나의 k*k 정사각형이 들어가게끔 나눠보면 || 모양 혹은 T모양으로 나누는 경우밖에 없다.
따라서 적절히 prefix sum의 누적 최댓값들을 미리 구해놓고 격자를 나누는 모든 모양에 대해 원유 함유량의 최댓값을 구하면 된다.

한 가지 주의해야 할 점은,
1개의 사업자가 차지할 수 있는 최대 원유량은 3개의 사업자 것보다 클 수 없지만
2개의 사업자가 차지할 수 있는 양은 3개의 사업자 것보다 클 수 있기 때문에 이를 잘 고려해야 한다.
ex)
9 5 3
0 0 0 0 0
0 0 0 0 0
0 0 1 1 1
0 0 1 1 1
0 0 1 1 1
1 1 1 0 0
1 1 1 0 0
1 1 1 0 0
0 0 0 0 0
답은 18이 아닌 16이다.

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int m, n, k, s[1501][1501], lm[3][1501], a[1501][1501], res;
int block(int x1, int y1, int x2, int y2) {
    return s[x2][y2] - s[x2][y1] - s[x1][y2] + s[x1][y1];
}
void f() {
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++) s[i][j] = a[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    int um[1501][1502] = {}, dm[1502][1502] = {};
    for (int i = k; i <= m; i++) {
        for (int j = k; j <= n; j++) {
            um[i][n + 1 - j] = max({ um[i - 1][n + 1 - j],um[i][n + 2 - j],block(i - k,n - j,i,n + k - j) });
            dm[m + 1 - i][n + 1 - j] = max({ dm[m + 2 - i][n + 1 - j],dm[m + 1 - i][n + 2 - j],block(m - i,n - j,m + k - i,n + k - j) });
        }
    }
    for (int j = k; j <= n; j++) {
        int t = 0;
        for (int i = k; i <= m; i++) t = max({ t, block(i - k, j - k, i, j) });
        lm[0][j] = max(t, lm[0][j - 1]);
        for (int i = k; i <= m - k; i++) res = max(res, lm[0][j] + um[i][j + 1] + dm[i + 1][j + 1]);
        if (j >= k * 2) lm[1][j] = max(lm[1][j - 1], lm[0][j - k] + t);
        if (j >= k * 3) lm[2][j] = max(lm[2][j - 1], lm[1][j - k] + t);
    }
    res = max(res, lm[2][n]);
}
int main() {
    scanf("%d%d%d", &m, &n, &k);
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++) scanf("%d", a[i] + j);
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            f();
            for (int x = 1; x <= m; x++) reverse(a[x] + 1, a[x] + 1 + n);
        }
        for (int x = 1; x <= 1500; x++)
            for (int y = x; y <= 1500; y++) swap(a[x][y], a[y][x]);
        swap(m, n);
    }
    printf("%d", res);
    return 0;
}

5922번: Above the Median

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

$O(n)$

간단히 x 이상인 수가 절반이상인 구간의 수를 구하면 된다.
a[i] = h[i]가 x이상이면 1, x미만이면 -1이라 하면
임의의 [i,j]에 대해 a[i...j] 합이 0이상인 경우 [i,j]는 x이상인 수가 절반이상이다.
따라서 누적합 s[i]=sum(a[1...i])을 이용하여 증가하는 j마다 i<j인 임의의 i에 대해 s[i]<=s[j]인 i의 수를 빠르게 찾으면 된다.

여기서부터는 펜윅트리 같은 자료구조를 이용해서 풀 수도 있지만,
i가 증가함에 따라 s[i]의 변화량이 최대 1 밖에 되지 않는다는 점을 착안하여 prefix sum을 이용하여 O(n)에 해결할 수 있다.


#include<cstdio>
int n, x, a[200001], p, t;
long long r, s = 1;
int main() {
    scanf("%d%d", &n, &x);
    a[p = n] = 1;
    while (n--) {
        scanf("%d", &t);
        t < x ? s -= a[p--] : s += a[++p];
        a[p]++; r += s++;
    }
    printf("%lld", r);
    return 0;
}

6233번: Face The Right Way

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


$O(n^2)$

임의의 k에 대해 기계의 최소 작동 횟수는 그리디 알고리즘을 통해 O(n)에 구할 수 있다.

k가 정해지면 앞의 소부터 보면서 뒤돌아 있는 경우 그 뒤 k마리의 방향을 바꿔주면 된다. 이를 빠르게 처리하기 위해 prefix sum과 비슷하게 기계의 누적 작동횟수와 이후 기계가 작동을 멈출 시점을 저장해준다.

#include<cstdio>
int n, k, m = 1e9;
char d[5000];
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf(" %c", d + i);
    for (int i = 1; i <= n; i++) {
        int cnt = 0, t = 0, j = 0, s[5001] = {};
        for (; j < n; j++) if ((t ^= s[j]) ^ d[j] == 'B') {
            if (j + i>n) break;
            cnt++;
            t ^= 1;
            s[j + i]++;
        }
        if (j == n && m>cnt) m = cnt, k = i;
    }
    printf("%d %d", k, m);
    return 0;
}

6231번: Gold Balanced Lineup

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


$O(kn\lg n)$

sum(a[0][i...j]) = sum(a[1][i...j]) = ... = sum(a[k-1][i...j])인 최대 j-i+1을 구하는 문제

s[i][j]=sum(s[i][1...j])라고 하면
s[0][j]-s[0][i-1] = s[1][j]-s[1][i-1] = ... = s[k-1][j]-s[i-1][i-1]
이와 동치인 연립등식은 다음과 같다.
s[0][j]-s[k-1][j] = s[0][i-1]-s[k-1][i-1]
s[1][j]-s[k-1][j] = s[1][i-1]-s[k-1][i-1]
...
s[k-2][j]-s[k-1][j] = s[k-2][i-1]-s[k-1][i-1]

이제 (s[0...k-2][i]-s[k-1][i])가 key인 해쉬를 이용하면 매 i마다 같은 key값의 최소 인덱스를 빠르게 구할 수 있다.

#include<cstdio>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
int n, k, r;
map<vector<int>, int> mp;
int main() {
    scanf("%d%d", &n, &k);
    vector<int> s(k);
    mp[s] = 0;
    for (int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        for (int j = k; j--; x /= 2) s[j] += x & 1;
        for (auto &it : s) it -= s[k - 1];
        auto it = mp.find(s);
        if (it != mp.end()) r = max(r, i - it->second);
        else mp[s] = i;
    }
    printf("%d", r);
    return 0;
}

14453번: Hoof, Paper, Scissors (Silver)

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


$O(n)$

임의의 경계에 대해 이를 기준으로 왼편과 오른편의 H, P, S만의 최대 개수 합의 최댓값이 답이 된다.

#include<cstdio>
int n, s[3][100001], res;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        char c;
        scanf(" %c", &c);
        for (int j = 0; j < 3; j++) s[j][i] += s[j][i - 1];
        s[(c - 'A') / 8][i]++;
    }
    for (int i = 1; i <= n; i++) {
        int l = 0, r = 0;
        for (int j = 0; j < 3; j++) {
            if (l < s[j][i]) l = s[j][i];
            if (r < s[j][n] - s[j][i]) r = s[j][n] - s[j][i];
        }
        if (res < l + r) res = l + r;
    }
    printf("%d", res);
    return 0;
}

8889번: 등고선 지도

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


$O(tmn\lg (mn))$

다각형을 이루는 x축과 평행한 선분들 중 아래는 +1 위는 -1로 표시하자.
그러면 어떤 점에서 아래로 가며 지나친 선분들의 값을 누적하면 그 점의 레벨을 알 수 있다.
모든 다각형에 대해 가장 왼쪽 아래의 점들을 배열에 저장하고 x를 기준으로 오름차순 정렬해놓는다.
주어진 선분들을 왼쪽에서 오른쪽으로 스위핑하면서 펜윅트리를 이용해 해당 점들 아래의 누적값을 구하여 최대 레벨을 구한다.

#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
const int MXM = 2e4, MXK = 1e2;
struct st {
    int x, y, c;
}sg[MXM*MXK];
int t, n, m, sz, my, bit[MXM*MXK / 2 + 1];
pair<intint> p[MXK], q[MXM];
int main() {
    for (scanf("%d", &t); t--;) {
        sz = my = 0;
        scanf("%d", &m);
        map<intint> mp;
        for (int i = 0; i < m; i++) {
            scanf("%d", &n);
            for (int j = 0; j < n; j++) scanf("%d%d", &p[j].first, &p[j].second), mp[p[j].second] = 0;
            int tp = min_element(p, p + n) - p;
            q[i] = p[tp];
            int j = tp;
            do {
                sg[sz++] = { p[j].first,p[j].second,1 };
                sg[sz++] = { p[(j + 1) % n].first,p[(j + 1) % n].second,-1 };
                j = (j + 2) % n;
            } while (tp^j);
        }
        for (auto &it : mp) it.second = ++my;
        fill(bit + 1, bit + 1 + my, 0);
        sort(sg, sg + sz, [](st i, st j) {return i.x < j.x; });
        sort(q, q + m);
        int res = 0;
        for (int i = 0, j = 0; i < m; i++) {
            for (; j < sz&&sg[j].x <= q[i].first; j++)
                for (int k = mp[sg[j].y]; k <= my; k += k&-k) bit[k] += sg[j].c;
            int s = 0;
            for (int k = mp[q[i].second]; k; k -= k&-k) s += bit[k];
            res = max(res, s);
        }
        printf("%d\n", res);
    }
    return 0;
}

1992번: 쿼드트리

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


$O(n^2)$


#include<cstdio>
int n, s[65][65];
void f(int xint yint l) {
    int t = s[x + l][y + l] - s[x][y + l] - s[x + l][y] + s[x][y];
    if (!t || t == l*l) printf("%d", t>0);
    else {
        printf("(");
        for (int i = 0; i < 4; i++) f(x + i / 2 * l / 2, y + i % 2 * l / 2, l / 2);
        printf(")");
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++)
        scanf("%1d", &s[i][j]), s[i][j] += s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];
    f(0, 0, n);
    return 0;
}

2015번: 수들의 합 4

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



$O(n\lg n)$

s[i]: 1...i 번째 수들의 합
s[i]-s[j]=k(j<i)인 i,j를 찾아야 하므로
모든 X=s[j]인 j(j<i)의 개수를 구해놓으면 해결할 수 있다.


#include<cstdio>
#include<map>
using namespace std;
int s, x, n, k;
map<intint> mp;
long long r;
int main() {
    mp[0]++;
    for (scanf("%d%d", &n, &k); n--;) {
        scanf("%d", &x);
        s += x;
        r += mp[s - k];
        mp[s]++;
    }
    printf("%lld", r);
    return 0;
}

11660번: 구간 합 구하기 5

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


$O(n^2+m)$

prefix sum을 이용한다.


#include<cstdio>
int n, m, s[1025][1025];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            scanf("%d", s[i] + j);
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
        }
    }
    for (int i = 0, x1, y1, x2, y2; i < m; i++) {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
    }
    return 0;
}

3351번: 삼각 분할

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


$O(n\lg n)$

삼각형을 노드, 인접한 삼각형끼리 간선으로 연결하면 트리가 된다. 이를 rooted 트리로 만들자.
같은 색의 노드를 모두 연결하는 최소 크기의 부분 트리에 대해 해당 트리에 존재하는 모든 간선들은 절단할 수 없다.
이러한 간선들은 lca + prefix sum을 이용하여 파악할 수 있다.
부분 트리의 리프 노드에 1씩 증가시켜주고 루트에 리프 노드 개수 만큼 차감시켜준다.
이제 루트부터 dfs를 돌며 누적값을 따져보면 절단 가능 여부를 판단할 수 있다.


#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MXN = 1e5 - 2;
pair <llint> p[MXN * 3 + 1];
vector<int> adj[MXN + 1], st[MXN + 3];
int n, dp[MXN + 1][17], d[MXN + 1], s[MXN + 1], r;
void f(int hint p) {
    for (auto it : adj[h]) if (it^p) {
        dp[it][0] = h;
        for (int i = 1; i < 17; i++) dp[it][i] = dp[dp[it][i - 1]][i - 1];
        d[it] = d[h] + 1;
        f(it, h);
    }
}
int lca(int xint y) {
    if (d[x] < d[y]) swap(xy);
    for (int i = 16; i >= 0; i--) if (1 << i <= d[x] - d[y]) x = dp[x][i];
    if (x == yreturn x;
    for (int i = 16; i >= 0; i--) if (dp[x][i] ^ dp[y][i]) x = dp[x][i], y = dp[y][i];
    return dp[x][0];
}
void g(int hint p) {
    for (auto it : adj[h]) if (it^p) {
        g(it, h);
        r += !s[it];
        s[h] += s[it];
    }
}
int main() {
    scanf("%d", &n);
    n -= 2;
    for (int i = 1, a[3], x; i <= n; i++) {
        scanf("%d%d%d%d", a, a + 1, a + 2, &x);
        sort(a, a + 3);
        p[i] = { (ll)a[0] * n + a[1],i };
        p[i + n] = { (ll)a[0] * n + a[2],i };
        p[i + 2 * n] = { (ll)a[1] * n + a[2],i };
        st[x].push_back(i);
    }
    sort(p + 1, p + 1 + 3 * n);
    for (int i = 2; i <= 3 * n; i++) if (p[i].first == p[i - 1].first) {
        adj[p[i].second].push_back(p[i - 1].second);
        adj[p[i - 1].second].push_back(p[i].second);
    }
    f(1, 0);
    for (int i = 1; i <= n + 2; i++) {
        for (int j = 1; j < st[i].size(); j++) {
            s[st[i][0]]++;
            s[st[i][j]]++;
            s[lca(st[i][0], st[i][j])] -= 2;
        }
    }
    g(1, 0);
    printf("%d", r);
    return 0;
}

1689번: 겹치는 선분

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


$O(n\lg n)$

시작 지점에 1, 끝 지점에 -1을 누적 시키고
앞부터 가중치를 더해가면 겹치는 선분 개수를 알 수 있다.


#include<cstdio>
#include<algorithm>
using namespace std;
pair<intint> p[2000000];
int r, s, n;
int main() {
    scanf("%d", &n);
    for (int i = 0, x, y; i < n; i++) {
        scanf("%d%d", &x, &y);
        p[i] = { x,1 };
        p[i + n] = { y,-1 };
    }
    sort(p, p + 2 * n);
    for (int i = 0; i < 2 * n; i++) {
        s += p[i].second;
        r = max(r, s);
    }
    printf("%d", r);
    return 0;
}

12746번: Traffic (Large)

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


$O((n+q)\lg n)$

주어진 그래프를 rooted tree 로 만든다.
(x,y) 쿼리가 들어오면 s[x]++, s[y]++, s[lca(x,y)]-=2를 해주고
루트를 시작점으로 dfs를 돌면서 자식의 s[]를 부모의 s[]에 누적시키면 모든 간선의 방문 수를 알 수 있다.
lca 쿼리를 O(lgn)이 되도록 구현해야 한다.


#include<cstdio>
#include<vector>
using namespace std;
const int MXN = 222222;
int n, q, dp[MXN + 1][18], dep[MXN + 1], s[MXN + 1], r;
vector<int> adj[MXN + 1];
pair<intint> t;
void f(int x) {
    for (auto it : adj[x]) {
        if (it == dp[x][0]) continue;
        dep[it] = dep[x] + 1;
        dp[it][0] = x;
        for (int i = 1; i<18; i++) dp[it][i] = dp[dp[it][i - 1]][i - 1];
        f(it);
    }
}
void g(int x) {
    for (auto it : adj[x]) if (it^dp[x][0]) {
        g(it);
        pair<intint> tp = { x,it };
        if (x>it) swap(tp.first, tp.second);
        if (s[it]>r || s[it] == r&&tp<t) {
            r = s[it];
            t = tp;
        }
        s[x] += s[it];
    }
}
int lca(int x, int y) {
    if (dep[x]<dep[y]) swap(x, y);
    for (int i = 17; i >= 0; i--)
        if (dep[x] - dep[y] >= 1 << i) x = dp[x][i];
    if (x == y) return x;
    for (int i = 17; i >= 0; i--)
        if (dp[x][i] ^ dp[y][i]) x = dp[x][i], y = dp[y][i];
    return dp[x][0];
}
int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1, x, y; i<n; i++) {
        scanf("%d%d", &x, &y);
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
    f(1);
    while (q--) {
        int x, y;
        scanf("%d%d", &x, &y);
        s[x]++;
        s[y]++;
        s[lca(x, y)] -= 2;
    }
    g(1);
    printf("%d %d %d", t.first, t.second, r);
    return 0;
}

11659번: 구간 합 구하기 4

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


$O(n+m)$

prefix sum 이용


#include<cstdio>
int n, m, s[100001];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", s + i), s[i] += s[i - 1];
    for (int i = 0, x, y; i < m; i++) {
        scanf("%d%d", &x, &y);
        printf("%d\n", s[y] - s[x - 1]);
    }
    return 0;
}

1451번: 직사각형으로 나누기

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


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

경계는 T자, ||자와 이것을 돌린 모양만 가능하다.
모든 경우를 고려해 답을 구하자.
직사각형 합은 prefix sum을 이용한다.


#include<cstdio>
#include<algorithm>
using namespace std;
int n, m;
long long s[101][101], r;
long long f(int x1, int y1, int x2, int y2) { return s[x2][y2] - s[x1][y2] - s[x2][y1] + s[x1][y1]; }
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) scanf("%1lld", &s[i][j]), s[i][j] += s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];
    for (int i = 1; i < n; i++)
        for (int j = 1; j < m; j++)
            r = max({ r,s[i][m] * f(i,0,n,j)*f(i,j,n,m),
                s[n][j] * f(0,j,i,m)*f(i,j,n,m),
                s[i][j] * f(0,j,n,m)*f(i,0,n,j),
                s[i][j] * f(0,j,i,m)*f(i,0,n,m) });
    for (int i = 1; i < n; i++)
        for (int j = i + 1; j <= n; j++) r = max(r, s[i][m] * f(i, 0, j, m)*f(j, 0, n, m));
    for (int i = 1; i < m; i++)
        for (int j = i + 1; j <= m; j++) r = max(r, s[n][i] * f(0, i, n, j)*f(0, j, n, m));
    printf("%lld", r);
    return 0;
}

3020번: 개똥벌레

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


$O(n+h)$

prefix sum을 이용한다.


#include<cstdio>
int n, h, c[500000], s, p = 1e9, q;
int main() {
    scanf("%d%d", &n, &h);
    for (int i = 0, x; i < n; i++) {
        scanf("%d", &x);
        i & 1 ? c[h - x]++ : (c[0]++, c[x]--);
    }
    for (int i = 0; i < h; i++) {
        s += c[i];
        if (s < p) p = s, q = 0;
        q += p == s;
    }
    printf("%d %d", p, q);
    return 0;
}

5541번: 釘

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


$O(n^2+m)$

prefix sum을 이용한다.
https://www.ioi-jp.org/joi/2011/2012-ho-prob_and_sol/2012-ho-t4-review.pdf
일본어지만 그림으로 풀이를 대강 유추할 수 있을 것이다.(설명이 필요하다면 덧글로)


#include<cstdio>
int n, m, r, s[5003][5003];
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0, x, y, z; i < m; i++) {
        scanf("%d %d %d", &x, &y, &z);
        s[x][y]++; s[x][y + 1]--;
        s[x + z + 1][y]--; s[x + z + 1][y + z + 2]++;
        s[x + z + 2][y + 1]++; s[x + z + 2][y + z + 2]--;
    }
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) s[i][j] += s[i - 1][j];
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) s[i][j] += s[i][j - 1];
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) r += (s[i][j] += s[i - 1][j - 1]) > 0;
    printf("%d", r);
    return 0;
}

10740번: ACM

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


$O(n)$

s(p,q): p번째 사람의 배열중 [1,q] 합
1,2,3 번째 사람이 하나씩 구간을 지정했다치자. ex) 첫 번째 [1,3], 두 번째 [4,5], 세 번째 [6,7]
각 구간이 순서대로 [1,i], [i+1,j], [j+1,n] 으로 지정 되어 있다면 이것이 나타내는 난이도 합은
r=s(3,n)-s(3,j) + s(2,j)-s(2,i) +s(1,i) 일 것이다.
주어진 문제는 이러한 r중에 가능한 최솟값을 찾는 것이 목표이다.
이를 해결하기 위해 j가 정해졌을 때 최솟값을 만드는 i를 지정하고 r을 구해보자.
1<=i<j<n 이므로
[1,i]에서 -s(2,i)+s(1,i)의 최솟값은 증가하는 j마다 한 번씩 구하여 최솟값을 갱신하며 저장하고 있으면 바로 쓸 수 있다.

구간을 지정하는 순서를 바꿔 최종적인 답을 구하자.


#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int n, r = 1e9;
vector<int> v[3];
int main() {
    scanf("%d", &n);
    for (int i = 0; i<3; i++)
        for (int j = 0, x; j<n; j++) scanf("%d", &x), v[i].push_back(x);
    sort(v, v + 3);
    do {
        int s = 0, s0 = v[0][0], s1 = v[1][0], s2 = 0, mini = 1e9;
        for (int i = 0; i<n; i++) s += v[0][i];
        for (int i = 1; i<n - 1; i++) {
            s2 += v[2][i - 1];
            mini = min(mini, s2 - s1);
            s0 += v[0][i];
            s1 += v[1][i];
            r = min(r, s - s0 + s1 + mini);
        }
    } while (next_permutation(v, v + 3));
    printf("%d", r);
    return 0;
}

10541번: 싸리와 버드의 피라미드

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


$O(lk+k\lg k)$

각 문자에 대한 누적합을 알고 있다하자. 이를 통해 어떤 구간에서 어떤 문자의 개수를 상수 시간 안에 알 수 있다.
a번째 줄의 어떤 문자의 개수를 구해야 하는데 이것은 a-1번째 줄에서 잘린 문자열과 a번째 문자열을 가지고 계산할 수 있다.
주어지는 n값이 매우 크므로 a의 제곱연산은 long long int 범위에 들어오지 않음을 유의해서 미리 문자열 길이로 나눈 나머지를 가지고 곱하는 방법을 사용하자.
각 문자에 대한 누적합을 배열에 미리 저장해 놓으려고 하면 메모리가 부족할 것이다.
따라서 입력으로 주어지는 쿼리들을 미리 받아놓고 알파벳순으로 정렬한뒤 필요한 문자에 대해서 그때마다 누적합을 구해 놓아야 한다.


#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX_L = 1e6, MAX_K = 5e4;
typedef long long ll;
ll n, res[MAX_K + 1], x, t;
int k, len, s[MAX_L + 1];
char str[MAX_L + 2];
struct st {
    ll idx, a;
    char c;
    bool operator<(st i) const {
        return c < i.c;
    }
}q[MAX_K + 1];
int main() {
    scanf("%lld %s %d", &n, str + 1, &k);
    len = strlen(str + 1);
    char c;
    for (int i = 1; i <= k; i++) scanf("%lld %c", &x, &c), q[i] = { i,x,c };
    sort(q + 1, q + 1 + k);
    for (int i = 1; i <= k; i++) {
        if (q[i - 1].c != q[i].c)
            for (int j = 1; j <= len; j++) s[j] = s[j - 1] + (str[j] == q[i].c);
        t = q[i].a % 2 ? (q[i].a - 1) / 2 % len*(q[i].a%len) % len
            : (q[i].a - 1) % len*(q[i].a / 2 % len) % len;
        res[q[i].idx] = (q[i].a + t) / len*s[len] + s[(q[i].a + t) % len] - s[t];
    }
    for (int i = 1; i <= k; i++) printf("%lld\n", res[i]);
    return 0;
}