Student. CSE

BOJ problem - solution 정리 ( - 24.08)

2024년 8월까지 BOJ 문제풀이 통합 (24.09.01)

24.04.11 문제풀이

24.04.11

이 날부터 사지방 연등을 하면서 푸는 문제들을 요약하여 올릴려고함.

class 6 문제 위주로 풀었음.

boj 13907 - 세금

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

  • 노드의 값이 바뀔때마다 최단경로를 구해야함
  • 노드의 값이 바뀔때마다 dijkstra를 실행하면 TLE ( O(KN + KMlgM))
  • dist를 2차원 배열로 바꾸어 dist(최종노드, 거쳐온 노드) 로 저장
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
int N, M, K, S, D;
vector<ii> g[1010];
vector<vector<int>> dist(1010, vector<int>(1010, 1e9));

void dijkstra() {
  priority_queue<iii> pq;
  dist[S][0] = 0;
  pq.push({0, S, 0});

  while (!pq.empty()) {
    int cur, cnt, curw;

    tie(curw, cur, cnt) = pq.top();
    curw *= -1;
    pq.pop();
    if (cnt > N)
      continue;
    if (dist[cur][cnt++] < curw)
      continue;

    for (auto e : g[cur]) {
      int nxt = e.first;
      int nxtw = e.second;

      if (dist[nxt][cnt] > curw + nxtw) {
        dist[nxt][cnt] = curw + nxtw;
        pq.push({-dist[nxt][cnt], nxt, cnt});
      }
    }
  }
}

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);
  cin >> N >> M >> K;
  cin >> S >> D;
  for (int i = 0; i < M; i++) {
    int a, b, w;
    cin >> a >> b >> w;
    g[a].push_back({b, w});
    g[b].push_back({a, w});
  }

  dijkstra();
  int ans = 1e9;
  for (int i = 0; i <= N; i++) {
    ans = min(ans, dist[D][i]);
  }
  cout << ans << "\n";
  while(K--) {
    int p;
    cin >> p;
    ans = 1e9;
    for (int i = 0; i < N; i++) {
      ans = min(ans, dist[D][i] = dist[D][i] + i * p);
    }
    cout << ans << "\n";
  }
}

boj 1948 - 임계경로

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

  • DAG 에서 최장 경로를 찾은 후, 해당 경로에 있는 노드의 개수를 카운팅

  • dfs를 이용하여 역추적으로 노드의 개수 세기 가능

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

vector<ii> g[10100], rg[10100];
vi ind(10100), d(10100);
vector<bool> vt(10100, false);
int cnt;
void f(int cur) {
  if (vt[cur])
    return;
  vt[cur] = true;
  for (auto x : rg[cur]) {
    int nxt = x.first;
    int w = x.second;
    if (d[cur] == d[nxt] + w) {
      f(nxt);
      cnt++;
    }
  }
}

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);
  int n, m;
  cin >> n >> m;

  while (m--) {
    int a, b, t;
    cin >> a >> b >> t;
    g[a].push_back({b, t});
    rg[b].push_back({a, t});
    ind[b]++;
  }
  int s, e;
  cin >> s >> e;
  queue<int> q;
  for (int i = 1; i <= n; i++) {
    if (ind[i] == 0)
      q.push(i);
  }
  while (!q.empty()) {
    int cur = q.front();
    q.pop();
    for (auto x : g[cur]) {
      int nxt = x.first;
      int w = x.second;
      if (d[nxt] < d[cur] + w) {
        d[nxt] = d[cur] + w;
      }
      if (--ind[nxt] == 0)
        q.push(nxt);
    }
  }
  f(e);
  int ans = 0;
  for (auto x : d)
    ans = max(ans, x);
  cout << ans << "\n" << cnt << "\n";
}

boj 1516 - 게임 개발

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

  • 위상정렬 + DP
  • 각 위상정렬마다의 최장 경로를 구해주면 됨
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

vi g[505];
int ind[505], d[505], c[505];

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);
  int n;
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> c[i];
    while (true) {
      int b;
      cin >> b;
      if (b == -1)
        break;
      g[b].push_back(i);
      ind[i]++;
    }
  }
  queue<int> q;
  for (int i = 1; i <= n; i++) {
    if (ind[i] == 0) {
      q.push(i);
      d[i] = c[i];
    }
  }
  while (!q.empty()) {
    int cur = q.front();
    q.pop();
    for (auto x : g[cur]) {
      int nxt = x;
      d[nxt] = max(d[nxt], d[cur] + c[nxt]);
      if (--ind[nxt] == 0)
        q.push(nxt);
    }
  }
  for (int i = 1; i <= n; i++) {
    cout << d[i] << endl;
  }
}

24.04.12-13 문제풀이

24.04.12 ~ 24.04.13

class 6 달성

고급 DP들과 기상한 풀이들을 공부하는것이 재밌어질 시기

BOJ 1471 - 사탕 돌리기

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

  • dfs를 이용하여 각 노드간 그래프를 만들어야함
  • 사이클 내 노드의 개수를 카운트 후 sz[i] 에 저장해야함

  • 각 자리수의 합을 찾는 기술 + 사이클의 노드 개수 카운팅
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

int to[202020], sz[202020], N;
bool vt[202020];

int dfs(int u) {
  int &ret = sz[u];
  if (ret != -1)
    return ret;
  vt[u] = true;
  int v = u, t = u;
  while (t > 0) {
    v += t % 10;
    t /= 10;
  }
  if (v > N)
    v -= N;
  to[u] = v;

  if (vt[v]) {
    if (sz[v] != -1)
      return ret = sz[v] + 1;
    ret = 1;
    for (int r = v; r != u; r = to[r])
      ++ret;
    for (int r = v; r != u; r = to[r])
      sz[r] = ret;
    return ret;
  }
  dfs(v);
  if (ret == -1)
    return ret = dfs(v) + 1;
  return ret;
}

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);

  cin >> N;
  memset(sz, -1, sizeof(sz));
  int ans = 0;
  for (int i = 1; i <= N; i++) {
    ans = max(ans, dfs(i));
  }
  cout << ans << '\n';
}

BOJ 1509 - 팰린드롬 분할

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

  • p[i][j] : 문자열의 i~j 까지가 팰린드롬인지 나타내는 배열
  • d[i] : 1~ i 까지의 최소분할 개수
  • 만약 j~i 가 팰린드롬인경우 d[i] = min(d[i] , d[j-1]+1)

위의 식이 성립하는 이유는 회문이 있는경우 분할 할 수 있는데, 이는 1~j-1 까지의 분할 개수와 j~i 의 회문 하나를 더한 것과 비교를 할 수 있다.

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
ll p[2525][2525], dp[2525];
int N;
string str;

int ispal(int i, int j) {
  ll &ret = p[i][j];
  if (ret != -1)
    return ret;
  if (i >= j)
    return 1;
  if (str[i] != str[j])
    return ret = 0;
  return ret = ispal(i + 1, j - 1);
}

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);

  cin >> str;
  N = str.size();
  str = ' ' + str;
  memset(p, -1, sizeof(p));

  dp[0] = 0;
  for (int i = 1; i <= N; i++) {
    dp[i] = 1e9;
    for (int j = 1; j <= i; j++) {
      if (ispal(j, i))dp[i] = min(dp[i], dp[j - 1] + 1);
    }
  }
  cout << dp[N] << '\n';
}

BOJ 1006 - 습격자 초라기

  • 환형 타일링 DP 문제

생각해야하는 부분이 많았던 문제. dp[N][0] - N번째에서 위만 채우는 최소 개수 dp[N][1] - N번쨰에서 아래만 채우는 최소 개수 dp[N][2] - N번째 둘다 채우는 최소 개수

  • i=2 일때부터 dp[i][0], dp[i][1], dp[i][2]를 순서대로 채워준다. 이때 e[i][k] 의 값에 따라 2개 블럭을 묶는 경우와 묶지 않는 경우를 나눠서 계산해준다.

  • 환형이기 때문에 1번째와 N번째의 관계에 따라 타일링을 다르게 해야한다. 각 경우에 따라 e[i][k]의 값을 무한으로 바꾸어 해당 칸에 대해서는 선택을 못하게끔 조정한다.

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
int dp[10101][3], e[10101][2], N, W;
void f() {
  for (int i = 2; i <= N; i++) {
    int p1 = (e[i - 1][0] + e[i][0] <= W) ? 1 : 2;
    int p2 = (e[i - 1][1] + e[i][1] <= W) ? 1 : 2;
    int p3 = (e[i][0] + e[i][1] <= W) ? 1 : 2;

    dp[i][0] = min(dp[i - 1][2] + 1, dp[i - 1][1] + p1);
    dp[i][1] = min(dp[i - 1][2] + 1, dp[i - 1][0] + p2);
    dp[i][2] = min({dp[i - 1][2] + p3, dp[i - 2][2] + p1 + p2, dp[i][0] + 1,
                    dp[i][1] + 1});
  }
}

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);

  int TC;
  cin >> TC;
  while (TC--) {

    cin >> N >> W;

    for (int i = 1; i <= N; i++)
      cin >> e[i][0];
    for (int i = 1; i <= N; i++)
      cin >> e[i][1];
    int ans = 1e9;

    
    int tmp1 = e[1][0];
    int tmp2 = e[1][1];
    dp[1][0] = dp[1][1] = 1;
    dp[1][2] = (e[1][0] + e[1][1] <= W) ? 1 : 2;
    f();
    ans = min(ans, dp[N][2]);

    memset(dp, 0, sizeof(dp));
    if (N != 1 && e[1][0] + e[N][0] <= W) {
      dp[1][0] = dp[1][1] = 1;
      dp[1][2] = 2;
      e[1][0] = 1e9;
      f();
      ans = min(ans, dp[N][1]);
      e[1][0] = tmp1;
      memset(dp, 0, sizeof(dp));
    }
    if (N != 1 && e[1][1] + e[N][1] <= W) {
      dp[1][0] = dp[1][1] = 1;
      dp[1][2] = 2;
      e[1][1] = 1e9;
      f();
      ans = min(ans, dp[N][0]);
      e[1][1] = tmp2;
      memset(dp, 0, sizeof(dp));
    }
    if (N != 1 && e[1][1] + e[N][1] <= W && e[1][0] + e[N][0] <= W) {
      dp[1][0] = dp[1][1] = 1;
      dp[1][2] = 2;
      e[1][0] = e[1][1] = 1e9;
      f();
      ans = min(ans, dp[N - 1][2]);
    }
    cout << ans << "\n";
  }
}

BOJ 13334 - 철로

  • priority_queue를 써서 풀어야하지만 집합을 이용한 기가막힌 풀이가 존재 해서 집합으로 풀이

  • $cnt(c) = [a_i , b_i] \subseteq [c, c+d]$ 의 개수를 만족해야 하는 cnt의 max를 찾으면 되는 문제

  • 즉 위의 식은 다음처럼 바꿀 수 있음 $cnt(c) = {i | a_i >= c} \cap { i| b_i <= c +d}$

$cnt(c) = A \cap B$ 로 줄일 수 있다. 이제 이 식을 풀어서 다음처럼 만들 수 있다.

$A \cap B = n(A) + n(B) + n(A^c \cap B^c) - n(U)$

이때 $A^c \cap B^c$를 공집합으로 만들어준다면 A의 개수, B의 개수, 전체 개수만으로도 답을 구할 수 있음.

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);

  int N, d;
  vi in, out;
  vector<ii> v;

  cin >> N;
  v.resize(N);
  for (int i = 0; i < N; i++) {
    cin >> v[i].first >> v[i].second;
    if (v[i].first > v[i].second)
      swap(v[i].first, v[i].second);
  }
  cin >> d;

  for (int i = 0; i < N; i++) {
    if (v[i].second - v[i].first > d)
      continue;
    in.push_back(v[i].first);
    out.push_back(v[i].second);
  }
  sort(in.begin(), in.end());
  sort(out.begin(), out.end());

  int c = -1, ans = 0;
  for (int i = 0; i < in.size(); i++) {
    if (in[i] == c)
      continue;
    c = in[i];
    int t = in.end() - lower_bound(in.begin(), in.end(), c);
    t += upper_bound(out.begin(), out.end(), c + d) - out.begin();
    t -= in.size();
    ans = max(ans, t);
  }
  cout << ans << endl;
}

24.04.14 문제풀이

24.04.14

BOJ 1019 - 책페이지

  • 1부터 N 까지 0~9 숫자가 몇번 나오는지 카운트하는 문제
  • 기본적인 탐색으로는 $O(N)$ 으로 TLE

  • x0 ~y9 까지는 0부터 9가 (y-x+1) 번씩 나온다는 것을 이용
  • 만약 xk 인 경우 x’0 으로 만들어 주기 위해 하나씩 더해가면서 해당 숫자의 자리에 0~9 까지를 카운트
  • yt 에서 y’9로 가는 것도 동일하게 카운트

  • x0 ~y9 까지 계산 완료시, x ~ y를 똑같이 반복하여 계산
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

ll ans[10];

void f(ll n, ll t) {
  while (n > 0) {
    ans[n % 10] += t;
    n /= 10;
  }
}

int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);

  ll s = 1, e, t = 1;

  cin >> e;

  while (s <= e) {
    while (s % 10 != 0 && s <= e) {
      f(s, t);
      s++;
    }
    if (s > e)
      break;
    while (e % 10 != 9 && s <= e) {
      f(e, t);
      e--;
    }
    ll cnt = (e / 10 - s / 10 + 1);
    for (int i = 0; i <= 9; i++) {
      ans[i] += cnt * t;
    }
    s /= 10;
    e /= 10;
    t *= 10LL;
  }
  for (int i = 0; i <= 9; i++) {
    cout << ans[i] << ' ';
  }
}

BOJ 2482 - 색상환

  • 환형 + 경우의수 + DP
  • D(i,j) = i번째에서 j개를 뽑는 경우의 수
  • $D(i,j) = D(i-2,j-1) + d(i-1,j)$ i번째를 사용하는 경우에는 i-1은 사용할 수 없으므로 (i-2, j-1)을 사용 i번째를 사용하지 않는 경우엔 (i-1,j)를 사용하면 됨.
  • 환형이기 때문에 N번째인경우 고려를 해줘야함

  • 환형 DP에서는 N-1까지는 선형으로 생각하고, N번째에서 N-1과 1번째를 고려해주면 편함
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;

const ll mod = 1e9 + 3;
ll D[1010][1010];
int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);

  int N, K;

  cin >> N >> K;
  for (int i = 1; i < 1001; i++) {
    D[i][0] = 1;
    D[i][1] = i;
  }

  for (int i = 2; i < N; i++) {
    for (int j = 2; j <= i; j++) {
      D[i][j] = (D[i - 2][j - 1] + D[i - 1][j]) % mod;
    }
  }
  cout << ((D[N - 3][K - 1] + D[N - 1][K]) % mod) << "\n";
}

BOJ 17626 - Four Squares

  • 기초 DP 문제
#define GG return 0
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
int D[50505], N;
int main(void) {
  ios::sync_with_stdio(0);
  cin.tie(0);
  
  cin >> N;
  D[1] = 1;
  for (int i = 2; i <= N; i++) {
     int ret = 1e9;
     for(int j = 1; j*j <=i;j++){
         ret = min(ret, D[i-j*j]);
     }
     D[i] = ret +1;
  }
  cout << D[N] << "\n";
}

24.04.15 문제풀이

24.04.15

BOJ 3176 - 도로 네트워크

  • sparse table + LCA

  • 노드에 대한 sparse table과 min, max의 정보가 담긴 sparse table 생성해야함

  • 두 노드의 lca를 구하고 각 노드와 lca 사이에서 min, max를 찾아야함

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

vector<ii> g[101010];
int depth[101010], st[101010][20],mx[101010][20], mn[101010][20], N;
int k, da, db;
bool vt[101010];
void dfs(int u,int d){
    vt[u] = true;
    depth[u] = d;
    for(auto v : g[u]){
        if(vt[v.first]) continue;
        st[v.first][0] = u;
        mx[v.first][0] = mn[v.first][0] = v.second;
        dfs(v.first,d+1);
    }
}

void table(){
    for(int j = 1; j<20;j++){
        for(int i = 1; i<=N;i++){
            st[i][j] = st[st[i][j-1]][j-1];
            mn[i][j] = min(mn[i][j-1], mn[st[i][j-1]][j-1]);
            mx[i][j] = max(mx[i][j-1], mx[st[i][j-1]][j-1]);
        }
    }
}

int lca(int u, int v){
    if(depth[u] < depth[v]) swap(u,v);
    int dif = depth[u] - depth[v];
    for(int i = 0; dif; i++){
        if(dif & 1) {
            u = st[u][i];
            
        }
        dif >>=1;
    }
    if(u == v) return u;
    for(int i = 19;i>=0;i--){
        if(st[u][i] != st[v][i]){
            u = st[u][i], v= st[v][i];
            
        }
    }
    return st[u][0];
}


int main(void){
    ios::sync_with_stdio(0);
  cin.tie(0);
  
  cin >> N;
  for(int i = 0; i<N-1;i++){
      int a,b,c; cin >> a >> b >>c;
      g[a].push_back({b,c});
      g[b].push_back({a,c});
  }

  dfs(1,0);
  table();
  
  int m ; cin >> m;
  while(m--){
      int a,b; cin >> a >> b;
      k = lca(a,b);
      da = depth[a] - depth[k], db = depth[b] - depth[k];
      int retmx =-1, retmn = 1e9;
      
      for(int i = 19; i>= 0;i--){
          if(da >= (1<< i)){
              retmx = max(retmx, mx[a][i]);
              retmn = min(retmn, mn[a][i]);
              da -=(1 << i);
              a = st[a][i];
          }
      }
      for(int i = 19; i>= 0;i--){
          if(db >= (1<< i)){
              retmx = max(retmx, mx[b][i]);
              retmn = min(retmn, mn[b][i]);
              db -=(1 << i);
              b = st[b][i];
          }
      }
      cout << retmn <<" " << retmx <<"\n";
  }
}

BOJ 3584 - 가장 가까운 공통 조상

  • tree root를 찾아서 dfs를 돌린 뒤 lca 적용
  • parent 정보만 있는 tree에서 root를 찾고, depth를 만드는 방법이 나와있음
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef vector < int >vi;
typedef pair < int, int >ii;
typedef tuple < int, int, int >iii;

vi g[101010];
int depth[101010], st[101010][20], N;
bool vt[101010];
void dfs (int u)
{
    for(int v : g[u]){
        if(depth[v] == -1){
                    depth[v] = depth[u] +1;
        st[v][0] =u;
        dfs(v);
        }

    }
}

void table ()
{
  for (int j = 1; j < 20; j++)
	{
	  for (int i = 1; i <= N; i++)
		{
		  st[i][j] = st[st[i][j - 1]][j - 1];
		}
	}
}

int lca (int u, int v)
{
  if (depth[u] < depth[v])
	swap (u, v);
  int dif = depth[u] - depth[v];
  for (int i = 0; dif; i++)
	{
	  if (dif & 1)
		{
		  u = st[u][i];

		}
	  dif >>= 1;
	}
  if (u == v)
	return u;
  for (int i = 19; i >= 0; i--)
	{
	  if (st[u][i] != st[v][i])
		{
		  u = st[u][i], v = st[v][i];

		}
	}
  return st[u][0];
}


int main (void)
{
  ios::sync_with_stdio (0);
  cin.tie (0);
  int TC;
  cin >> TC;
  while (TC--)
	{
	  memset (st, 0, sizeof (st));
	  memset (depth, 0, sizeof (depth));
	  memset (vt, false, sizeof (vt));
	  for (int i = 0; i < 101010; i++)
		{
		  g[i].clear ();
		}
	  cin >> N;
	  for (int i = 0; i < N - 1; i++)
		{
		  int a, b;
		  cin >> a >> b;
		  g[a].push_back (b);
		  ++depth[b];
		}
	   int r;
	   for(int i = 1; i<=N;i++){
	       if(depth[i] == 0){
	         r = i;  
	       }
	   }
	   memset(depth, -1, sizeof(depth));
	   depth[r] = 0;
	  dfs (r);
	  table ();

	  int a, b;
	  cin >> a >> b;
	  cout << lca (a, b) << "\n";

	}



}

노드가 N개인 K진 트리 생성

for (int i = 1, k = 2;; i++)
	{
	  int z = K;
	  for (; z > 0; k++, z--)
		{


		  g[i].push_back (k);
		  if (k == N)
			return;
		}
	}

24.04.16

Tarjan’s Algorithm

SCC를 찾는 알고리즘으로 $O(V+E)$에 시간복잡도를 가지고 있다. dfs Tree에서

order[i] : i번째 정점이 몇번째 방문인지,

low[i] : i번 정점을 루트로 하는 서브 트리에서 간선하나로 갈수 있는 정점중 가장 작은 low값을 의미함.

BOJ 11266 - 단절점

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

  • Tarjan’s Algorithm을 이용하여 단절점을 구할 수 있음

  • 단절점의 조건은 다음과 같음

    1. V가 단절점이 아님 –> V의 자손 중 V를 거치지 않고 한번에 V위의 정점(방문정점) 을 갈 수 있음
    2. V가 root 라면 자식이 2개 이상인 경우 단절점

이 조건을 이용하여 Naive 방식을 제외한 Tarjan’s Algorihtm을 이용하여 코드 작성

  • 제시되는것이 하나의 컴포넌트 그래프가 아닐 수 있으므로, vt[i]를 사용하여 컴포넌트 그래프마다 확인해야함
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

vi g[10101];
int order[10101], low[10101],t;
bool vt[10101];
set<int> ans;

void dfs(int u, int par){
    order[u] = t++;
    low[u] = t;
    vt[u] = true;
    int sub = 0;
    for(auto v : g[u]){
        if(!vt[v]){
            sub++;
            dfs(v,u);
            low[u] = min(low[u],low[v]);
            if(par != -1 && low[v] >= order[u]) ans.insert(u);
        }
        else if(v != par) low[u] = min(low[u], order[v]);
    }
    if(par == -1 && sub > 1) ans.insert(u);
}

int main(void){
    FASTIO;
    int V,E;
    cin >> V >> E;
    while(E--){
        int u,v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    for(int i = 1; i<=V;i++){
        if(!vt[i]) dfs(i,-1);
    }
    cout << ans.size() <<"\n";
    for(auto i : ans) cout << i <<" ";
}

BOJ 11400 - 단절선

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

  • tarjan’s algorithm을 이용하여 구현
  • 다음 조건을 만족 시 단절선
    1. 현재 정점 A와 자식 정점B를 잇는 간선에서A의 자손들 중 A를 거치지 않고, A 이전 방문 정점에갈수 없는 경우
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

vi g[101010];
int order[101010], low[101010],t;
bool vt[101010];
vector<ii> ans;

void dfs(int u, int par){
    order[u] = t++;
    low[u] = t;
    for(auto v : g[u]){
        if(v == par) continue;
        if(order[v] == 0){
            dfs(v,u);
            low[u] = min(low[u],low[v]);
            if(low[v] > order[u]) ans.push_back({min(u,v), max(u,v)});
        }
        else low[u] = min(low[u], order[v]);
    }
}

int main(void){
    FASTIO;
    int V,E;
    cin >> V >> E;
    while(E--){
        int u,v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    dfs(1,-1);
    cout << ans.size() <<"\n";
    sort(ans.begin(), ans.end());
    for(auto i : ans) cout << i.first <<" " <<i.second<<"\n";
}

BOJ 11308 - One-Way Road

  • 단절선을 찾는 문제
  • 역으로, 단절선이 있지 않은 경우 이어지는 모든 간선을 찾으면 됨

중복되는 간선이 있을 수 있으므로, 간선 처리 시 g[x][y]로 사용하였음

#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);

using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;

int g[55][55];
int order[55], low[55],t;
vi ans[55];
int V,E;
bool dfs(int u, int par){
    bool res = 0;
    order[u] = low[u] = ++t;
    for(int v = 1; v<=V;v++) if(g[u][v]){
        g[u][v]--;
        g[v][u]--;
        if(v == par) continue;
        ans[v].push_back(u);
        if(order[v] == 0){
            res |= dfs(v,u);
            low[u] = min(low[u],low[v]);
        }
        else low[u] = min(low[u], order[v]);
        if(order[u] < low[v]) res = 1;
    }
    return res;
}

int main(void){
    FASTIO;
    int TC; cin >>TC;
    while(TC--){
        for(int i = 0;i<55;i++) {ans[i].clear();}
        memset(order,0,sizeof(order));
        memset(low,0,sizeof(low));
        memset(g,0,sizeof(g));
        t = 0;
        
        cin >> V >> E;
        while(E--){
            int u,v; cin >> u >> v;
            g[v][u]++;
            g[u][v]++;
        }
        
        bool f = dfs(1,-1);
        if(f) cout << "NO"<<"\n";
        else{
            cout << "YES\n";
            for(int i = 1; i<=V;i++){
                
                for(auto j : ans[i]){
                    cout << i <<" " <<j <<"\n";
                }
            }
        }

    }
    
}

24.04.16 문제풀이

24.04.17

인접행렬의 거듭제곱

  • 0과 1로 이루어진 인접행렬에 대해서 i->j 를 1로 생각하는경우, 해당 인접행렬의 N제곱은 i에서 N번 이동했을때 j로 가는 경우의 수를 의미함.

BOJ 1533 - 길의 개수

  • 인접행렬의 거듭제곱
  • 해당 행렬은 binary matrix가 아니기 때문에 인접행렬의 거듭제곱 기법을 사용할 수 없음 –> 정점을 분할하여 binary matrix로 만들어 사용

  • 해당 행렬을 5배로 늘려, i5+0, i5+1 … , i*5+4 에 각각 v0, v1,, v4 를 할당시켜줌

  • v4-> v3 을 1의 가중치를 넣어줌 (다른 vVn -> Vn-1 도 동일하게)
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);

#define x first
#define y second
#define all(v) v.begin(), v.end()

using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
const ll mod = 1000003;
struct Matrix{
	int size;
	vector< vector<ll> > arr;
	Matrix(){size = 0;}
	Matrix(int n){
		size = n;
		arr = vector< vector<ll> >(n, vector<ll>(n));
	}
	Matrix operator * (const Matrix &x){
		Matrix ret(size);
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++){
				for(int k=0; k<size; k++){
					ret.arr[i][j] += arr[i][k] * x.arr[k][j];
					ret.arr[i][j] %= mod;
				}
			}
		}
		return ret;
	}
};

Matrix powmat(Matrix a, ll b){
	if(b == 1) return a;
	Matrix ret = powmat(a, b/2);
	ret = ret * ret;
	if(b & 1) ret = ret * a;
	return ret;
}


int main(void){
    FASTIO
    int N,S,E,T; cin>> N >> S >> E >>T;
    
    S--; E--;
    
    Matrix mat(N*5);
    
    for(int i = 0;i<N;i++){
        for(int j = 1; j<5;j++){
            mat.arr[i*5+j][i*5+j-1] = 1;
        }
    }
    
    for(int i = 0;i<N;i++){
        string str; cin >> str;
        
        for(int j = 0;j<N;j++){
            int t = str[j] - '0';
            if(t == 1) mat.arr[i*5][j*5] = 1;
            else if(t > 1) mat.arr[i*5][j*5+t-1] =1;
        }
    }
    
    Matrix ans = powmat(mat,T);
    cout << ans.arr[S*5][E*5];
}

BOJ 12850 - 본대산책2

  • 인접행렬을 이용한 거듭제곱을 이용한 문제
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);

#define x first
#define y second
#define all(v) v.begin(), v.end()

using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
const ll mod = 1000000007;
struct Matrix{
	int size;
	vector< vector<ll> > arr;
	Matrix(){size = 0;}
	Matrix(int n){
		size = n;
		arr = vector< vector<ll> >(n, vector<ll>(n));
	}
	Matrix operator * (const Matrix &x){
		Matrix ret(size);
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++){
				for(int k=0; k<size; k++){
					ret.arr[i][j] += arr[i][k] * x.arr[k][j];
					ret.arr[i][j] %= mod;
				}
			}
		}
		return ret;
	}
};

Matrix powmat(Matrix a, ll b){
	if(b == 1) return a;
	Matrix ret = powmat(a, b/2);
	ret = ret * ret;
	if(b & 1) ret = ret * a;
	return ret;
}


int main(void){
    FASTIO
    int D; cin>> D;
    Matrix mat(8);
    mat.arr = {
        {0, 1, 1, 0, 0, 0, 0, 0},
        {1, 0, 1, 1, 0, 0, 0, 0},
        {1, 1, 0, 1, 1, 0, 0, 0},
        {0, 1, 1, 0, 1, 1, 0, 0},
        {0, 0, 1, 1, 0, 1, 0, 1},
        {0, 0, 0, 1, 1, 0, 1, 0},
        {0, 0, 0, 0, 0, 1, 0, 1},
        {0, 0, 0, 0, 1, 0, 1, 0}
};
    
    Matrix ans = powmat(mat,D);
    cout << ans.arr[0][0];
}

BOJ 14289 - 본대산책3

  • 인접행렬을 이용한 거듭제곱 문제
  • 본대산책 2와 동일
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);

#define x first
#define y second
#define all(v) v.begin(), v.end()

using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
const ll mod = 1000000007;
struct Matrix{
	int size;
	vector< vector<ll> > arr;
	Matrix(){size = 0;}
	Matrix(int n){
		size = n;
		arr = vector< vector<ll> >(n, vector<ll>(n));
	}
	Matrix operator * (const Matrix &x){
		Matrix ret(size);
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++){
				for(int k=0; k<size; k++){
					ret.arr[i][j] += arr[i][k] * x.arr[k][j];
					ret.arr[i][j] %= mod;
				}
			}
		}
		return ret;
	}
};

Matrix powmat(Matrix a, ll b){
	if(b == 1) return a;
	Matrix ret = powmat(a, b/2);
	ret = ret * ret;
	if(b & 1) ret = ret * a;
	return ret;
}


int main(void){
    FASTIO
    int n,m,D; cin>>n >> m;
    Matrix mat(n);
    for(int i = 0;i<m;i++){
        int a,b; cin >> a >> b;
        a--,b--;
        mat.arr[a][b] =mat.arr[b][a] = 1;
    }
    cin >> D;
    Matrix ans = powmat(mat,D);
    cout << ans.arr[0][0];
}

24.04.20 문제풀이

24.04.20

2일동안 사지방 연등을 신청하지 못해서 아쉽게도 일지를 못올렸었다.

2-SAT 문제

$ L = (a_1 \vee b_1 ) \wedge (a_2 \vee b_2 ) \wedge (a_3 \vee b_3 ) \wedge … (a_m \vee b_m ) \wedge $

$L$ 이 참이거나 그런 조합이 없음을 나타내어야함

  • implication graph를 이용하여 SCC를 구한 후 $x_i$과 $ \neg x_i$ 이 모두 같은 scc에 속해있지 않으면 됨

  • implication graph
    • 노드 : $x_i$ , $ \neg x_i $
    • 간선 : 변수간 관계 -> $(x_i \vee y_i)$ 는 $\neg x_i \rightarrow y_i$ 와 $\neg y_i \rightarrow x_i$
  • 2-SAT 문제를 많이 접해보기 위해 kks227 님의 문제 목록을 기준으로 풀것임. https://m.blog.naver.com/kks227/220803009418

  • 담주부터 바빠져서 작성이 늦어질수도.

    BOJ 11280 - 2-SAT 3

  • 2-sat 기본 문제
#include <bits/stdc++.h>
using namespace std;

vector<int> g[20202], rev[20202],vv;

bool vt[20202];
int scc[20202];
inline int notX(int x){ return x ^ 1; }
inline int trueX(int x){ return x << 1; }
inline int falseX(int x){ return x << 1 | 1; }
void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    vt[u] = 1;
    scc[u] = c;
    for(auto v : rev[u]) if(!vt[v]) revdfs(v,c);
}

void getscc(int n){
    memset(vt,false,sizeof(vt));
    for(int i = 2; i<=n*2 +1;i++){
        if(!vt[i]) dfs(i);
    }
    memset(vt, false, sizeof vt);
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!vt[i]) {revdfs(i,cnt); cnt++;}
        
    }
}

int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int n, m; cin >> n >> m;
    
    for(int i = 0;i<m;i++){
        int a,b; cin >> a >> b;
        if(a > 0) a = trueX(a);
        else a = falseX(-a);
        if(b>0) b = trueX(b);
        else b = falseX(-b);
        g[notX(a)].push_back(b);
        g[notX(b)].push_back(a);
        rev[b].push_back(notX(a));
        rev[a].push_back(notX(b));
    }
    
    getscc(n);
    
    for(int i = 1; i<=n;i++){
        if(scc[trueX(i)] == scc[falseX(i)]){
            cout << "0"; return 0;
        }
    }
    cout << 1 <<"\n";
}

BOJ 3747 - 완벽한 선거!

  • while(cin » n » m) 으로 eof 를 정해주면 되는 문제
#include <bits/stdc++.h>
using namespace std;

vector<int> g[20202], rev[20202],vv;

bool vt[20202];
int scc[20202];
inline int notX(int x){ return x ^ 1; }
inline int trueX(int x){ return x << 1; }
inline int falseX(int x){ return x << 1 | 1; }
void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    vt[u] = 1;
    scc[u] = c;
    for(auto v : rev[u]) if(!vt[v]) revdfs(v,c);
}

void getscc(int n){
    memset(vt,false,sizeof(vt));
    for(int i = 2; i<=n*2 +1;i++){
        if(!vt[i]) dfs(i);
    }
    memset(vt, false, sizeof vt);
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!vt[i]) {revdfs(i,cnt); cnt++;}
        
    }
}

int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    int n, m; 
    
    while(cin >> n >> m){
        for(int i = 0; i <=4*n;i++){
            g[i].clear();
            rev[i].clear();
        }
        vv.clear();
        memset(scc,0,sizeof scc);
        
        for(int i = 0;i<m;i++){
            int a,b; cin >> a >> b;
            if(a > 0) a = trueX(a);
            else a = falseX(-a);
            if(b>0) b = trueX(b);
            else b = falseX(-b);
            g[notX(a)].push_back(b);
            g[notX(b)].push_back(a);
            rev[b].push_back(notX(a));
            rev[a].push_back(notX(b));
        }
        
        getscc(n);
        bool flag = false;
        for(int i = 1; i<=n;i++){
            if(scc[trueX(i)] == scc[falseX(i)]){
                flag = true; break;
            }
        }
        cout << (flag ? 0 : 1) <<"\n";
    }
}

BOJ 2207 - 가위바위보

  • 위의 문제와 똑같은 문제
  • n과m의 자리를 바꾸면 됨
#include <bits/stdc++.h>
using namespace std;

vector<int> g[20202], rev[20202],vv;

bool vt[20202];
int scc[20202];
inline int notX(int x){ return x ^ 1; }
inline int trueX(int x){ return x << 1; }
inline int falseX(int x){ return x << 1 | 1; }
void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    vt[u] = 1;
    scc[u] = c;
    for(auto v : rev[u]) if(!vt[v]) revdfs(v,c);
}

void getscc(int n){
    memset(vt,false,sizeof(vt));
    for(int i = 2; i<=n*2 +1;i++){
        if(!vt[i]) dfs(i);
    }
    memset(vt, false, sizeof vt);
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!vt[i]) {revdfs(i,cnt); cnt++;}
        
    }
}

int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int n, m; cin >> m >> n;
    
    for(int i = 0;i<m;i++){
        int a,b; cin >> a >> b;
        if(a > 0) a = trueX(a);
        else a = falseX(-a);
        if(b>0) b = trueX(b);
        else b = falseX(-b);
        g[notX(a)].push_back(b);
        g[notX(b)].push_back(a);
        rev[b].push_back(notX(a));
        rev[a].push_back(notX(b));
    }
    
    getscc(n);
    
    for(int i = 1; i<=n;i++){
        if(scc[trueX(i)] == scc[falseX(i)]){
            cout << "OTL"; return 0;
        }
    }
    cout << "^_^" <<"\n";
}

BOJ 2519 - 막대기

  • 2-SAT 응용 + 선분교차

  • 첫 다이아몬드 문제
  • https://justicehui.github.io/koi/2019/05/21/BOJ2519/ 를 참고

  • i 번쨰 사람은 3i , 3i+1, 3i+2 막대기를 가지고, 그중 하나를 제거할수 있으므로,

3i - $\neg$3i+1 , 3i - $\neg$3i+12 3i+1 - $\neg$3i, 3i+1 - $\neg$3i+1 3i+2 - $\neg$3i, 3i+2 - $\neg$3i+1

다음처럼 함의그래프를 이어준다.

또한 막대기의 교차또한 안되므로, x,y가 교차하는 경우에 $\neg$x -> y , $\neg$ y -> x 를 넣어준다.

scc를 돌리고 여부와, 참인 경우에 제거 막대기를 출력한다.

#include <bits/stdc++.h>
using namespace std;



vector<int> g[20202], rev[20202],vv;
int X1[3030], Y1[3030],X2[3030],Y2[3030];
bool vt[20202];
int scc[20202];
int n;
inline int inv(int x){
	if(x >= 3*n) return x - 3*n;
	else return x + 3*n;
}

int ccw(int a, int b, int c, int d, int e, int f){
	int res = (c-a)*(f-b) - (d-b)*(e-a);
	return res > 0 ? 1 : -1;
}

bool cross(int i, int j){
	int t1 = ccw(X1[i], Y1[i], X2[i], Y2[i], X1[j], Y1[j]) * ccw(X1[i], Y1[i], X2[i], Y2[i], X2[j], Y2[j]);
	int t2 = ccw(X1[j], Y1[j], X2[j], Y2[j], X1[i], Y1[i]) * ccw(X1[j], Y1[j], X2[j], Y2[j], X2[i], Y2[i]);
	return t1 < 0 && t2 < 0;
}


void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    scc[u] = c;
    for(auto v : rev[u]) if(!scc[v]) revdfs(v,c);
}

void getscc(){
    for(int i = 0; i<n*6;i++){
        if(!vt[i]) dfs(i);
    }
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!scc[i]) revdfs(i,cnt++);
        
    }
}
void add(int a, int b){
	g[a].push_back(b);
	rev[b].push_back(a);
	g[inv(b)].push_back(inv(a));
	rev[inv(a)].push_back(inv(b));
}
int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cin >> n;
    
    for(int i = 0; i<3*n;i++){
        cin >> X1[i] >> Y1[i] >> X2[i] >> Y2[i];
    }
    for(int i = 0;i<n;i++){
        add(3*i, inv(3*i+1));
        add(3*i, inv(3*i+2));
        add(3*i+1, inv(3*i));
        add(3*i+1, inv(3*i+2));
        add(3*i+2, inv(3*i));
        add(3*i+2, inv(3*i+1));
        
    }
    for(int i = 0;i<3*n;i++){
        for(int j = i+1; j<3*n;j++){
            if(cross(i,j)) add(inv(i),j);
        }
    }
    getscc();
    vector<int> ans;
	for(int i=0; i<3*n; i++){
		if(scc[i] == scc[inv(i)]){
			cout << -1; return 0;
		}
		if(scc[i] > scc[inv(i)]) ans.push_back(i+1);
	}
	cout << ans.size() << "\n";
	for(auto i : ans) cout << i << " ";
}

24.04.21 문제풀이

24.04.21

4월 21일도 2-sat 문제에 대해 적응하는 시간을 가졌다.

BOJ 7535 - A Bug’s Life

  • kks227님의 문제 리스트중 하나
  • 2-sat으로 구할수 있음.

  • 문자열 출력에서 빙빙 돌았던 문제
#include <bits/stdc++.h>
using namespace std;

vector<int> g[4040], rev[4040],vv;

bool vt[4040];
int scc[4040];
inline int notX(int x){ return x ^ 1; }
inline int trueX(int x){ return x << 1; }
inline int falseX(int x){ return x << 1 | 1; }
void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    vt[u] = 1;
    scc[u] = c;
    for(auto v : rev[u]) if(!vt[v]) revdfs(v,c);
}

void getscc(int n){
    memset(vt,false,sizeof(vt));
    for(int i = 2; i<n*2 +1;i++){
        if(!vt[i]) dfs(i);
    }
    memset(vt, false, sizeof vt);
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!vt[i]) {revdfs(i,cnt); cnt++;}
        
    }
}

int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    int n, m, T;
    cin >> T;
    for(int t = 1; t<=T;t++){
        cin >> n >> m;
        for(int i = 0; i <4040;i++){
            g[i].clear();
            rev[i].clear();
        }
        vv.clear();
        memset(scc,0,sizeof scc);
        
        for(int i = 0;i<m;i++){
            int a,b; cin >> a >> b;
            if(a > 0) a = trueX(a);
            else a = falseX(-a);
            if(b>0) b = trueX(b);
            else b = falseX(-b);
            g[notX(a)].push_back(b);
            g[a].push_back(notX(b));
            g[notX(b)].push_back(a);
            g[b].push_back(notX(a));
            rev[b].push_back(notX(a));
            rev[a].push_back(notX(b));
            rev[notX(a)].push_back(b);
            rev[notX(b)].push_back(a);
        }

        getscc(n);
        bool flag = true;
        for(int i = 1; i<=n;i++){
            if(scc[trueX(i)] == scc[falseX(i)]){
                flag = false;
                break;
            }
        } 
        cout << "Scenario #"<<t<<":\n";
        cout << (flag ? "No s" : "S") << "uspicious bugs found!\n\n";
    }
}

BOJ 3648 - 아이돌

  • 2-sat 문제
  • $x_1 \vee x_1$ 이어야 하므로 해당 식을 g에 넣어준 후 구현

#include <bits/stdc++.h>
using namespace std;

vector<int> g[2020], rev[2020],vv;

bool vt[2020];
int scc[2020];
inline int notX(int x){ return x ^ 1; }
inline int trueX(int x){ return x << 1; }
inline int falseX(int x){ return x << 1 | 1; }
void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    vt[u] = 1;
    scc[u] = c;
    for(auto v : rev[u]) if(!vt[v]) revdfs(v,c);
}

void getscc(int n){
    memset(vt,false,sizeof(vt));
    for(int i = 2; i<n*2 +1;i++){
        if(!vt[i]) dfs(i);
    }
    memset(vt, false, sizeof vt);
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!vt[i]) {revdfs(i,cnt); cnt++;}
        
    }
}

int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    int n, m; 
    while(cin >> n >> m){
        for(int i = 0; i <2020;i++){
            g[i].clear();
            rev[i].clear();
        }
        vv.clear();
        memset(scc,0,sizeof scc);
        
        for(int i = 0;i<m;i++){
            int a,b; cin >> a >> b;
            if(a > 0) a = trueX(a);
            else a = falseX(-a);
            if(b>0) b = trueX(b);
            else b = falseX(-b);
            g[notX(a)].push_back(b);
            g[notX(b)].push_back(a);
            rev[b].push_ba24.04.20 문제풀이

### 24.04.20

2일동안 사지방 연등을 신청하지 못해서 아쉽게도 일지를 못올렸었다.

#### 2-SAT 문제
$ L = (a_1 \vee b_1 ) \wedge (a_2 \vee b_2 ) \wedge (a_3 \vee b_3 ) \wedge ... (a_m \vee b_m ) \wedge $

$L$ 이 참이거나 그런 조합이 없음을 나타내어야함

- implication graph를 이용하여 SCC를 구한 후 $x_i$과 $ \neg x_i$ 이 모두 같은 scc에 속해있지 않으면 됨

- implication graph
	- 노드 : $x_i$ , $ \neg x_i $
	- 간선 : 변수간 관계 -> $(x_i \vee y_i)$ 는 $\neg x_i \rightarrow y_i$ 와 $\neg y_i \rightarrow x_i$

- 2-SAT 문제를 많이 접해보기 위해 kks227 님의 문제 목록을 기준으로 풀것임.
https://m.blog.naver.com/kks227/220803009418

- 담주부터 바빠져서  작성이 늦어질수도.
#### BOJ 11280 - 2-SAT 3

- 2-sat 기본 문제

#include <bits/stdc++.h> using namespace std;

vector g[20202], rev[20202],vv;

bool vt[20202]; int scc[20202]; inline int notX(int x){ return x ^ 1; } inline int trueX(int x){ return x « 1; } inline int falseX(int x){ return x « 1 | 1; } void dfs(int u){ vt[u] = true; for(auto v : g[u]) if(!vt[v]) dfs(v); vv.push_back(u); }

void revdfs(int u, int c){ vt[u] = 1; scc[u] = c; for(auto v : rev[u]) if(!vt[v]) revdfs(v,c); }

void getscc(int n){ memset(vt,false,sizeof(vt)); for(int i = 2; i<=n*2 +1;i++){ if(!vt[i]) dfs(i); } memset(vt, false, sizeof vt); reverse(vv.begin(), vv.end()); int cnt = 1; for(auto i : vv){ if(!vt[i]) {revdfs(i,cnt); cnt++;}

} }

int main(void){ ios_base::sync_with_stdio(0); cin.tie(0); int n, m; cin » n » m;

for(int i = 0;i<m;i++){
    int a,b; cin >> a >> b;
    if(a > 0) a = trueX(a);
    else a = falseX(-a);
    if(b>0) b = trueX(b);
    else b = falseX(-b);
    g[notX(a)].push_back(b);
    g[notX(b)].push_back(a);
    rev[b].push_back(notX(a));
    rev[a].push_back(notX(b));
}

getscc(n);

for(int i = 1; i<=n;i++){
    if(scc[trueX(i)] == scc[falseX(i)]){
        cout << "0"; return 0;
    }
}
cout << 1 <<"\n"; } ```

BOJ 3747 - 완벽한 선거!

  • while(cin » n » m) 으로 eof 를 정해주면 되는 문제
#include <bits/stdc++.h>
using namespace std;

vector<int> g[20202], rev[20202],vv;

bool vt[20202];
int scc[20202];
inline int notX(int x){ return x ^ 1; }
inline int trueX(int x){ return x << 1; }
inline int falseX(int x){ return x << 1 | 1; }
void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    vt[u] = 1;
    scc[u] = c;
    for(auto v : rev[u]) if(!vt[v]) revdfs(v,c);
}

void getscc(int n){
    memset(vt,false,sizeof(vt));
    for(int i = 2; i<=n*2 +1;i++){
        if(!vt[i]) dfs(i);
    }
    memset(vt, false, sizeof vt);
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!vt[i]) {revdfs(i,cnt); cnt++;}
        
    }
}

int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    int n, m; 
    
    while(cin >> n >> m){
        for(int i = 0; i <=4*n;i++){
            g[i].clear();
            rev[i].clear();
        }
        vv.clear();
        memset(scc,0,sizeof scc);
        
        for(int i = 0;i<m;i++){
            int a,b; cin >> a >> b;
            if(a > 0) a = trueX(a);
            else a = falseX(-a);
            if(b>0) b = trueX(b);
            else b = falseX(-b);
            g[notX(a)].push_back(b);
            g[notX(b)].push_back(a);
            rev[b].push_back(notX(a));
            rev[a].push_back(notX(b));
        }
        
        getscc(n);
        bool flag = false;
        for(int i = 1; i<=n;i++){
            if(scc[trueX(i)] == scc[falseX(i)]){
                flag = true; break;
            }
        }
        cout << (flag ? 0 : 1) <<"\n";
    }
}

BOJ 2207 - 가위바위보

  • 위의 문제와 똑같은 문제
  • n과m의 자리를 바꾸면 됨
#include <bits/stdc++.h>
using namespace std;

vector<int> g[20202], rev[20202],vv;

bool vt[20202];
int scc[20202];
inline int notX(int x){ return x ^ 1; }
inline int trueX(int x){ return x << 1; }
inline int falseX(int x){ return x << 1 | 1; }
void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    vt[u] = 1;
    scc[u] = c;
    for(auto v : rev[u]) if(!vt[v]) revdfs(v,c);
}

void getscc(int n){
    memset(vt,false,sizeof(vt));
    for(int i = 2; i<=n*2 +1;i++){
        if(!vt[i]) dfs(i);
    }
    memset(vt, false, sizeof vt);
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!vt[i]) {revdfs(i,cnt); cnt++;}
        
    }
}

int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int n, m; cin >> m >> n;
    
    for(int i = 0;i<m;i++){
        int a,b; cin >> a >> b;
        if(a > 0) a = trueX(a);
        else a = falseX(-a);
        if(b>0) b = trueX(b);
        else b = falseX(-b);
        g[notX(a)].push_back(b);
        g[notX(b)].push_back(a);
        rev[b].push_back(notX(a));
        rev[a].push_back(notX(b));
    }
    
    getscc(n);
    
    for(int i = 1; i<=n;i++){
        if(scc[trueX(i)] == scc[falseX(i)]){
            cout << "OTL"; return 0;
        }
    }
    cout << "^_^" <<"\n";
}

BOJ 2519 - 막대기

  • 2-SAT 응용 + 선분교차

  • 첫 다이아몬드 문제
  • https://justicehui.github.io/koi/2019/05/21/BOJ2519/ 를 참고

  • i 번쨰 사람은 3i , 3i+1, 3i+2 막대기를 가지고, 그중 하나를 제거할수 있으므로,

3i - $\neg$3i+1 , 3i - $\neg$3i+12 3i+1 - $\neg$3i, 3i+1 - $\neg$3i+1 3i+2 - $\neg$3i, 3i+2 - $\neg$3i+1

다음처럼 함의그래프를 이어준다.

또한 막대기의 교차또한 안되므로, x,y가 교차하는 경우에 $\neg$x -> y , $\neg$ y -> x 를 넣어준다.

scc를 돌리고 여부와, 참인 경우에 제거 막대기를 출력한다.

#include <bits/stdc++.h>
using namespace std;



vector<int> g[20202], rev[20202],vv;
int X1[3030], Y1[3030],X2[3030],Y2[3030];
bool vt[20202];
int scc[20202];
int n;
inline int inv(int x){
	if(x >= 3*n) return x - 3*n;
	else return x + 3*n;
}

int ccw(int a, int b, int c, int d, int e, int f){
	int res = (c-a)*(f-b) - (d-b)*(e-a);
	return res > 0 ? 1 : -1;
}

bool cross(int i, int j){
	int t1 = ccw(X1[i], Y1[i], X2[i], Y2[i], X1[j], Y1[j]) * ccw(X1[i], Y1[i], X2[i], Y2[i], X2[j], Y2[j]);
	int t2 = ccw(X1[j], Y1[j], X2[j], Y2[j], X1[i], Y1[i]) * ccw(X1[j], Y1[j], X2[j], Y2[j], X2[i], Y2[i]);
	return t1 < 0 && t2 < 0;
}


void dfs(int u){
    vt[u] = true;
    for(auto v : g[u]) if(!vt[v]) dfs(v);
    vv.push_back(u);
}

void revdfs(int u, int c){
    scc[u] = c;
    for(auto v : rev[u]) if(!scc[v]) revdfs(v,c);
}

void getscc(){
    for(int i = 0; i<n*6;i++){
        if(!vt[i]) dfs(i);
    }
    reverse(vv.begin(), vv.end());
    int cnt = 1;
    for(auto i : vv){
        if(!scc[i]) revdfs(i,cnt++);
        
    }
}
void add(int a, int b){
	g[a].push_back(b);
	rev[b].push_back(a);
	g[inv(b)].push_back(inv(a));
	rev[inv(a)].push_back(inv(b));
}
int main(void){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cin >> n;
    
    for(int i = 0; i<3*n;i++){
        cin >> X1[i] >> Y1[i] >> X2[i] >> Y2[i];
    }
    for(int i = 0;i<n;i++){
        add(3*i, inv(3*i+1));
        add(3*i, inv(3*i+2));
        add(3*i+1, inv(3*i));
        add(3*i+1, inv(3*i+2));
        add(3*i+2, inv(3*i));
        add(3*i+2, inv(3*i+1));
        
    }
    for(int i = 0;i<3*n;i++){
        for(int j = i+1; j<3*n;j++){
            if(cross(i,j)) add(inv(i),j);
        }
    }
    getscc();
    vector<int> ans;
	for(int i=0; i<3*n; i++){
		if(scc[i] == scc[inv(i)]){
			cout << -1; return 0;
		}
		if(scc[i] > scc[inv(i)]) ans.push_back(i+1);
	}
	cout << ans.size() << "\n";
	for(auto i : ans) cout << i << " ";
}

ck(notX(a)); rev[a].push_back(notX(b)); } int o = trueX(1); g[notX(o)].push_back(o); rev[o].push_back(notX(o)); getscc(n); bool flag = true; for(int i = 1; i<=n;i++){ if(scc[trueX(i)] == scc[falseX(i)]){ cout « “no\n”; flag = false; break; } } if(flag) cout « “yes\n”; } }


24.04.22 문제풀이

### 24.04.22

인접행렬 거듭제곱 문제풀이.

2-SAT 4 문제풀이 해야함 --> code 정비

#### BOJ 17401 - 일하는 세포

- 인접행렬의 거듭제곱 이용
- $ ABCABC \neq A^2 B^2 C^2 $ .. 즉 행렬의 교환법칙 성립은 안됨
- $ (ABC)^{D/T} * AB .. $ 이런식으로 계산해야함

- D가 0 인경우가 있으므로, 거듭제곱시 주의 (메모리 초과 이유)

#include <bits/stdc++.h> using namespace std;

typedef long long ll;

const ll mod = 1000000007; int T, N ,D;

struct Matrix{ int size; vector< vector > arr; Matrix(){ size = N; arr = vector< vector >(N, vector(N,0)); } Matrix operator * (const Matrix &x){ Matrix ret; for(int i=0; i<size; i++){ for(int j=0; j<size; j++){ for(int k=0; k<size; k++){ ret.arr[i][j] += arr[i][k] * x.arr[k][j]; ret.arr[i][j] %= mod; } } } return ret; } };

Matrix powmat(Matrix a, ll b){ if(b == 0) { Matrix I; for(int i = 0;i<N;i++) I.arr[i][i] = 1; return I; }; if(b == 1) return a; Matrix ret = powmat(a, b/2); ret = ret * ret; if(b & 1) ret = ret * a; return ret; }

int main(void){ cin » T » N »D; Matrix mat[101];

for(int i = 0;i<N;i++){
    mat[0].arr[i][i]= 1;
}

for(int i = 1; i<=T;i++){
    int M; cin >> M;
    while(M--){
        int a,b,c; cin >> a >> b >> c;
        a--; b--;
        mat[i].arr[a][b] = c;
    }
}
for(int i = 1; i<=T;i++){
    mat[0] = mat[0] * mat[i];
}
mat[0] = powmat(mat[0], 1LL * D / T);

for(int i = 1; i<= D%T;i++){
    mat[0] = mat[0] * mat[i];
}
for(int i = 0;i<N;i++){
    for(int j = 0;j<N;j++){
        cout << mat[0].arr[i][j] <<" ";
    }
    cout << "\n";
} } ```

BOJ 13876 - 타일채우기 2

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const ll mod = 1000000007;
ll N;


struct Matrix{
	int size;
	vector< vector<ll> > arr;
	Matrix(){size = 0;}
	Matrix(int n){
		size =n;
		arr = vector< vector<ll> >(n, vector<ll>(n,0));
	}
	Matrix operator * (const Matrix &x){
		Matrix ret(size);
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++){
				for(int k=0; k<size; k++){
					ret.arr[i][j] += arr[i][k] * x.arr[k][j];
					ret.arr[i][j] = (ret.arr[i][j] +mod)% mod;
				}
			}
		}
		return ret;
	}
};

Matrix powmat(Matrix a, ll b){
	if(b == 1) return a;
	Matrix ret = powmat(a, b/2);
	ret = ret * ret;
	if(b & 1) ret = ret * a;
	return ret;
}


int main(void){
    cin >> N;
    if(N&1){
        cout << 0 <<"\n";
        return 0;
    }
    Matrix M(2),P(2);
    M.arr = { {4,-1} , {1,0} };
    P.arr = { {3,0} , {1,0} };
    M = powmat(M, N/2) * P;

    cout << M.arr[1][0] % mod;
    
}

BOJ 13430 - 합 구하기

  • $S(k,n) = {n+k} C{k+1}$ 와 같다.

  • 간단히 이항계수 계산으로 풀어주면 됨
  • 좀더 간단한 inv 계산 식 사용
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll mod = 1e9+7;

ll pow(ll n,ll k){
    ll ret = 1;
    k <<= 1;
    while(k >>= 1){
        if(k&1) ret = ret * n% mod;
        n = n*n % mod;
    }
    return ret;
}

ll fac(ll a,ll b){
    ll ret = 1;
    for(ll i = a; i<= b;i++){
        ret = ret* i % mod;
    }
    return ret;
}

int main(void){
    ll N, K;
    cin >> K >> N;
    
    ll ans = fac(N,N+K) * pow(fac(1,K+1),mod-2) % mod;
    cout << ans<< "\n";
}

24.04.23~26 문제풀이

24.04.23 ~ 26

KOI 2019 1차 대회 문제풀이 이제 조금씩 대회 문제무작위로 풀이하기 시작

BOJ 17609 - 회문

  • 회문 판정 알고리즘 이용
  • dp 까지는 사용안해도 시간 안에 들어옴
#include <bits/stdc++.h>
using namespace std;

string str;
int g(int i , int j){
    if (i >= j)
        return 0;
    if (str[i] != str[j]){
        return 2;
    }
    return g(i + 1, j - 1);
}
int f(int i,int j){
    if (i >= j)
        return 0;
    if (str[i] != str[j]){
        int ret1 = g(i+1, j);
        int ret2 = g(i, j-1);
        if(ret1 == 0 || ret2 == 0) return 1;
        else return 2;
    }
    return f(i + 1, j - 1);
}

int main(void){
    int T; cin >> T;
    while(T--){
        cin >> str;
        cout << f(0, str.size()-1) << "\n";
    }
}

BOJ 17610 - 양팔저울

  • 브루트포스 알고리즘 문제
#include <bits/stdc++.h>
using namespace std;

int N,sum, ans,S[14], c[4040404];

void f(int n, int k){
    if(n == N){
        if(k >0) c[k] =1;
        return;
    }
    f(n+1, k+S[n]);
    f(n+1, k-S[n]);
    f(n+1, k);
}

int main(void){
    cin >> N;
    
    for(int i = 0; i<N;i++) cin >> S[i];
    for(int i = 0; i<N;i++) sum += S[i];
    
    f(0,0);
    for(int i = 1;i<sum;i++){
        if(!c[i]) ans++;
    }
    cout <<ans <<"\n";
    
}

BOJ 17612 - 쇼핑몰

  • 동일 순번인 경우 계산대가 큰 순서대로 퇴출 -> pq 사용
  • 이후 작은 계산대부터 채워넣어야함 -> stack 사용

  • 두개의 자료구조를 잘 사용하여 풀어야 하는 문제
#include <bits/stdc++.h>
using namespace std;

int main(void){
    
    int N, K;
    cin >> N >> K;
    long long ans = 0;
    vector<pair<int,int>> v;
    priority_queue<tuple<int,int,int>> pq;
    for(int i = 1; i<=N;i++){
        int r,t;
        cin >> r >> t;
        v.push_back({r,t});
        if(i <= K) pq.push({-t,i,r});
    }
    int cnt = 1, idx = K;
    
    
    stack<pair<int,int>> s;
    while(!pq.empty()){
        int qr, qt, qcur; tie(qt,qcur,qr) = pq.top();
        qt *= -1;
        
        int nr,nt,ncur;
        nr = qr; nt = qt; ncur = qcur;
        while(!pq.empty() && qt == nt){
            s.push({nt, ncur});
            pq.pop();
            ans += 1LL * nr *( cnt++);
            
            tie(nt,ncur,nr) = pq.top();
            nt *= -1;
        }
        
        while(!s.empty()){
            if(pq.size() < K && idx < N){
                int nt = s.top().first, ncur = s.top().second;
                s.pop();
                pq.push({-(nt + v[idx].second), ncur, v[idx++].first});
            }
            else break;
        }
    }
    
    
    cout << ans;
}

24.04.27 문제풀이

24.04.27

sweeping 문제풀이

BOJ 1911 - 흙길 보수하기

  • 스위핑 문제
  • 물웅덩이 좌표를 시작좌표 기준으로 정렬한 후 왼쪽에서 부터 웅덩이가 있는 부분에 판자를 채워주면 됨
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
const int INF = 1e9+1;

int main(void){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int N , L;
    vector <pair<ll,ll>> v;
    
    cin >> N >> L;
    for(int i = 0;i<N;i++){
        ll x, y; cin >> x >> y;
        v.push_back({x,y});
    }
    
    sort(v.begin(), v.end());
    
    ll ans= 0 ,cnt = 0, x = 0;
    
    for(int i = 0;i<N;i++){
        x = max(x, v[i].first);
        if( v[i].second < x) continue;
        int tmp = ceil((v[i].second- x)/ (double) L);
        ans += tmp;
        x += L*tmp;
        
    }
    cout << ans ;
    
}

BOJ 2170 - 선긋기

  • 스위핑 기초문제
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
const int INF = 1e9+1;

int main(void){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int N;
    vector <pair<ll,ll>> v;
    
    cin >> N;
    for(int i = 0;i<N;i++){
        ll x, y; cin >> x >> y;
        v.push_back({x,y});
    }
    
    sort(v.begin(), v.end());
    
    ll ans = 0, l = -INF, r = -INF;
    
    for(int i = 0;i<N;i++){
        if(r < v[i].first){
            ans += r-l;
            l = v[i].first;
            r = v[i].second;
        }
        else r = max(r, v[i].second);
    }
    ans += r-l;
    cout << ans ;
    
}

BOJ 17619 - 개구리 점프

  • 스위핑 문제
  • y좌표를 요구받지만, 문제에서는 y 좌표는 상관 없음 –> 선긋기 문제와 동일

  • 인덱스별로 union-find 자료구조를 통해 집합을 만들어줌
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
const int INF = 1e9+1;

int par[101010];

void init(int n){
    for(int i = 1; i<=n;i++){
        par[i] = i;
    }
}

int find(int u){
    if(u == par[u]) return u;
    return par[u] = find(par[u]);
}

void merge(int u,int v){
    u = find(u), v = find(v);
    if(u == v) return;
    par[u]  = v;
}


int main(void){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int N , Q;
    vector <iii> v;
    
    cin >> N >> Q;
    for(int i = 0;i<N;i++){
        int x1, x2, y; cin >> x1 >> x2 >> y;
        v.push_back({x1,x2, i+1});
    }
    init(N);
    sort(v.begin(), v.end());
    int r = get<1>(v[0]);
    for(int i = 1; i<N;i++){
        int x1,x2, idx; tie(x1,x2,idx) = v[i];
        if(x1 <= r){
            merge(get<2>(v[i-1]), idx);
            r = max(r, x2);
        } 
        else r = x2;
    }
    
    
    while(Q--){
        int a,b; cin>> a >> b;
        cout << ( find(a) == find(b) ? 1 : 0) <<"\n";
    }
    
}

BOJ 10165 - 버스 노선

  • 환형 스위핑 문제
  • 개구리 점프를 환형으로 구현하면 됨
  • 환형 인 경우, 노선을 0~N 을 0 ~ 2N 으로 연장 후 x1, x2를 입력 받고, $ x1 > x2 $ 일때 x2에 N 을 더해줌
  • $ x2 <= N $ 에 한해서 x1+N, x2+N 을 v에 넣어줌

  • 이때 시작점은 오름차순, 끝점은 내림차순으로 정렬하기 위해 -를 붙여줌
 
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;


int ans[505050];


int main(void){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int N , M;
    vector <iii> v;
    
    cin >> N >> M;
    for(int i = 0;i<M;i++){
        int x1, x2; cin >> x1 >> x2;
        if(x1 > x2) x2 += N;
        if(x2 <= N) v.push_back({x1+N, -(x2+N), i+1});
        v.push_back({x1,-x2, i+1});
        
    }
    sort(v.begin(), v.end());
    int l = 0, r = 0;
    for(int i = 0; i<v.size();i++){;
        int x1,x2, idx; tie(x1,x2,idx) = v[i];
        x2 *= -1;
        if(ans[idx]) continue;
        if(l <= x1 && x2 <= r){
            ans[idx] = true;
            continue;
        } 
        l = x1, r = x2;
    }
    for(int i = 1; i<=M;i++){
        if(!ans[i]) cout << i <<" ";
    }

}

KOI 2018 초등부 문제풀이 ( 24.04.29 )

총평

처음으로 풀어본 KOI 완셋이다 + 올클 확실히 실력이 많이 올랐음을 직감하였음 3번문제까지는 무난하게 풀었고, 4번 문제는 로직보단 자료구조를 어떤것을 사용할것인가 생각하는 것이 조금 오래 걸렸음

KOI 2018 초등부 1번

  • 단순히 주어진 입력값에서 최대와 최소를 찾아서 차이를 구하는 문제

  • 굳이 배열을 사용할 필요 없음

KOI 2018 초등부 2번

  • 브루트포스로 풀수 있는 문제
  • $O(N^2)$ 의 시간복잡도를 가짐

KOI 2018 초등부 3번

  • 정점이 N개 간선이 N-1개 이므로 트리 문제
  • dfs를 이용하여 시점과 종점을 잇는 길을 찾은 후, 해당 길에서 가장 큰 가중치를 가지는 값을 전체 path의 길이에서 빼주면 된다.

  • 주의해야할건 시점과 종점이 같은 경우 바로 0으로 처리해주면 된다.
#include <bits/stdc++.h>
using namespace std;

int N, s, e;
vector<pair<int,int>> g[101010];
vector<int> ans;
void dfs(int u, int p){
	if(u == e){
		int ret = 0;
		for(int i = 0; i<ans.size();i++){
			ret += ans[i];
		}
		int mx = *max_element(ans.begin(), ans.end());
		cout << ret - mx <<"\n";
		exit(0);
	}
	for(auto v : g[u]){
		if(v.first == p) continue;
		ans.push_back(v.second);
		dfs(v.first, u);
		ans.pop_back();
	}
}

int main(void){
	cin >> N >> s >> e;
	for(int i =0;i<N-1;i++){
		int u,v,w; cin >> u >> v >> w;
		g[u].push_back({v,w});
		g[v].push_back({u,w});
	}
	if (s == e) cout << 0<<"\n";
	else dfs(s,0);
}

KOI 2018 초등부 4번 (☆)

  • 다익스트라 알고리즘을 변형하여 구현하면 되는 문제
  • 다만, 어떤 자료구조를 사용할 것인가에 대해 코드가 복잡해질수 있음
  • 인접리스트를 사용하여 각 NM 배열의 각 좌표를 1부터 NM에 대응시켜 품

  • 외곽에 구멍이 있는 경우 0과 연결시켜 주었음
  • 외곽과 연결된 곳들을 먼저 pq에 넣어주고 풀이 진행
#include <bits/stdc++.h>
using namespace std;

int N , M ,H;
vector<pair<int,int>> g[1010101];
int V[1010101];

priority_queue<tuple<int,int,int>> pq;

int main(void){
    cin >> N >> M >> H;
	V[0] = 0;
    for(int i = 1; i<= N+1;i++){
        for(int j = 1; j<=M;j++){
            int k; cin >> k;
            if(k != -1){
                int idx = (i-1)*M +j;
                g[(idx <=N*M ? idx : 0)].push_back({(idx-M > 0 ? idx-M : 0),k});
                g[(idx-M > 0 ? idx-M : 0)].push_back({(idx <=N*M ? idx : 0),k});
            }
        }
    }
    for(int i = 1; i<= N;i++){
        for(int j = 1; j<=M+1;j++){
            int k; cin >> k;
            if(k != -1){
                int idx = (i-1)*M +j;
                g[(j == M+1 ? 0 : idx)].push_back({(j == 1 ? 0 : idx-1),k});
                g[(j == 1 ? 0 : idx-1)].push_back({(j == M+1 ? 0 : idx),k});
            }
        }
    }
	for(int i = 1; i<=N*M;i++){
	    V[i] = H;
		for(auto k : g[i]){
			if(k.first == 0){
				pq.push({-k.second, i, k.first});
			}
		}
	}
    while(!pq.empty()){
        int h, c, p; tie(h,c,p) = pq.top();
        h *= -1;
        pq.pop();
        if(V[c] > max(h, V[p])){
            V[c] = max(h,V[p]);
            for(auto nxt : g[c]){
            	if(nxt.first == 0) continue;
                pq.push({-nxt.second, nxt.first, c});
            }
        }
    }
    int ans = 0;
    for(int i = 1; i<=N*M;i++) ans += V[i];
    cout << ans;
}

Lazy Propagation을 Bottom up 으로 구현

BOJ 10999 - 구간 합 구하기 2

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

ll t[4040400], d[4040400];
ll n,h;

void calc(ll p, ll k) {
  if (d[p] == 0) t[p] = t[p<<1] + t[p<<1|1];
  else t[p] = d[p] * k;
}

void apply(ll p, ll value, ll k) {
  t[p] += value * k;
  if (p < n) d[p] += value;
}
void build(ll l, ll r) {
  ll k = 2;
  for (l += n, r += n-1; l > 1; k <<= 1) {
    l >>= 1, r >>= 1;
    for (ll i = r; i >= l; --i) calc(i, k);
  }
}

void push(ll l, ll r) {
  ll s = h, k = 1 << (h-1);
  for (l += n, r += n-1; s > 0; --s, k >>= 1)
    for (ll i = l >> s; i <= r >> s; ++i) if (d[i] != 0) {
      apply(i<<1, d[i], k);
      apply(i<<1|1, d[i], k);
      d[i] = 0;
    }
}

void modify(ll l, ll r, ll value) {
  if (value == 0) return;
  push(l, l + 1);
  push(r - 1, r);
  bool cl = false, cr = false;
  ll k = 1;
  for (l += n, r += n; l < r; l >>= 1, r >>= 1, k <<= 1) {
    if (cl) calc(l - 1, k);
    if (cr) calc(r, k);
    if (l&1) apply(l++, value, k), cl = true;
    if (r&1) apply(--r, value, k), cr = true;
  }
  for (--l; r > 0; l >>= 1, r >>= 1, k <<= 1) {
    if (cl) calc(l, k);
    if (cr && (!cl || l != r)) calc(r, k);
  }
}

ll query(int l, int r) {
  push(l, l + 1);
  push(r - 1, r);
  ll res = 0;
  for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
    if (l&1) res += t[l++];
    if (r&1) res += t[--r];
  }
  return res;
}
int main(void){
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	int N, M ,K; cin >> N >> M >> K;
	
	
	n = 1;
	while(n < N) n <<=1;
	h = sizeof(int) * 8 - __builtin_clz(n);
	for(ll i = 0;i<N;i++) cin >> t[i+n];
	for(int i = n-1;i;--i) t[i] = t[i<<1]+t[i<<1|1];
	int T = M+K;
	while(T--){
		int a;
		cin >> a;
		if(a == 1){
			ll b,c,d;
			cin >> b >> c >> d;
			
			modify(b-1,c,d);
		}
		if(a == 2){
			ll b,c;
			cin>> b >> c;
			cout << query(b-1,c)<<"\n";
		}
	}
}

4월의 마지막 달 문제

앞으로 5월부터는 주차별로 풀이를 올려야겠다.

BOJ 6549 - 히스토그램에서 가장 큰 직사각형

  • segment Tree에는 최소값의 인덱스를 저장해야함
  • 분할정복을 이용하여 넓이의 최대값을 구해주면 됨 –> 포인트
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int INF = 1e9+3;
ll t[404040], A[101010];
ll n;

inline ll combine(ll a, ll b){
	int ta = (a==-1 ? INF : A[a]);
	int tb = (b == -1 ? INF : A[b]);
	if(ta <= tb) return a;
	else return b;
}

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = combine(t[i<<1], t[i<<1 | 1]);
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>>=1;) t[p] = combine( t[p<<1], t[p<<1|1]);
}

ll query(int l, int r){
	ll resl = -1, resr = -1;
	for(l += n, r+= n;  l<r;l>>=1, r>>= 1){
		if(l&1) resl = combine(resl, t[l++]); 
		if(r&1) resr = combine(t[--r], resr);
	}
	return combine(resl, resr);
}

ll largest(int s, int e){
    
	int m = query(s,e+1);
	ll area = (e-s+1)*A[m]*1LL;
	if(s <= m-1) {
		ll temp = largest(s,m-1);
		area = max(area, temp);
	}
	if(m+1 <= e){
		ll temp = largest(m+1, e);
		area = max(area, temp);
	}
	return area;
}


int main(void){
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	
	int N;
	while(true){
		cin >> N;
		if(N == 0) break;
		n = 1;
		while(n < N) n <<=1;
		for(ll i = 0;i<N;i++){
			cin >> A[i];
			t[i+n] = i;
		};
		for(ll i = N;i<n;i++) A[i] = INF,  t[i+n] = i;
		build();
		cout << largest(0,N-1)<<"\n";
	}
	
}

BOJ 5676 - 음주 코딩

  • 곱한 값은 수가 커질 수 있으므로, 양수인 경우 1, 음수인 경우 -1 로 바꿔서 세그먼트 트리에 저장한다.
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

ll t[4040400];
ll n;

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = t[i<<1] * t[i<<1 | 1];
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>>=1;) t[p] = t[p<<1]* t[p<<1|1];
}

ll query(int l, int r){
	ll res =1;
	for(l += n, r+= n;  l<r;l>>=1, r>>= 1){
		if(l&1) res *= t[l++];
		if(r&1) res *= t[--r];
	}
	return res;
}

int main(void){
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	
	int N,K;
	while(cin >> N  >>  K){
		
		n = 1;
		while(n < N) n <<=1;
		for(ll i = 0;i<N;i++){
			int tmp; cin >> tmp;
			t[i+n] = (tmp >= 0 ?(tmp == 0 ? 0 : 1) :-1 );
		};
		for(ll i = N;i<n;i++) t[i+n] = 1;
		build();
		
		while(K--){

			char C;
			int i,j; cin >> C >> i >> j;
			if(C == 'C'){
				update(i-1,(j == 0 ? 0 : (j > 0 ? 1 : -1)));
			}
			else{
				int temp = query(i-1,j);
				if(temp == 0) cout << 0;
				else if(temp == 1) cout << '+';
				else cout << '-';
			}
			
		}
		cout << "\n";
		
	}
	
}

2024년 5월 문제풀이 (최근 수정 : 2024.05.04)

BOJ 12837 - 가계부 (Hard)

  • 세그먼트 트리
  • p의 위치의 값을 v로 바꾸는 것이 아닌 p의 값 에 v를 더해야함
  • 트리의 기본값은 0이므로 따로 수를 추가할 필요가 없음
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

ll t[4040400];
ll n;

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = t[i<<1] + t[i<<1 | 1];
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>1; p>>= 1) t[p>>1] = t[p] + t[p^1];
}

ll query(int l, int r){
	ll res = 0;
	for(l += n, r+= n;  l<r;l>>=1, r>>= 1){
		if(l&1) res += t[l++];
		if(r&1) res += t[--r];
	}
	return res;
}

int main(void){
	FASTIO
	int N, Q;
	cin >> N >>Q;
	
	n = 1;
	while(n < N) n <<=1;
	while(Q--){
		ll a,b,c; cin >> a >> b  >> c;
		if(a&1) update(b-1,t[b-1+n] + c);
		else cout <<  query(b-1,c) <<"\n";
	}
}

BOJ 1275 - 커피숍2

  • 세그먼트 트리
  • x와 y의 값이 바뀔수도 있으므로 주의해야함
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

ll t[4040400];
ll n;

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = t[i<<1] + t[i<<1 | 1];
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>1; p>>= 1) t[p>>1] = t[p] + t[p^1];
}

ll query(int l, int r){
	ll res = 0;
	for(l += n, r+= n;  l<r;l>>=1, r>>= 1){
		if(l&1) res += t[l++];
		if(r&1) res += t[--r];
	}
	return res;
}

int main(void){
	FASTIO
	int N, Q;
	cin >> N >>Q;
	
	n = 1;
	while(n < N) n <<=1;
	for(ll i = 0;i<N;i++) cin >> t[i+n];
	build();
	while(Q--){
		ll x,y,a,b; cin >> x  >> y >> a >> b;
		if(x>y) swap(x,y);
		cout << query(x-1,y) <<"\n";
		update(a-1, b);
	}
}

BOJ 1517 - 버블 소트

  • 세그먼트 트리 문제

  • swap의 개수 = 원배열과 정배열(정렬된)을 같은 수끼리 이어나갈때, 교차점의 개수를 세는 것과 같음 –> Inversion counting 이라 한다함
  • 트리의 기본값을 모두 1로 해놓고, [0, 원배열(i)) 의 1의 합을 구한 후, 원배열(i)를 0으로 바꿔주면 됨

  • 좌표 압축을 통해 간단히 변형
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

ll t[4040400];
ll n;

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = t[i<<1] + t[i<<1 | 1];
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>1; p>>= 1) t[p>>1] = t[p] + t[p^1];
}

ll query(int l, int r){
	ll res = 0;
	for(l += n, r+= n;  l<r;l>>=1, r>>= 1){
		if(l&1) res += t[l++];
		if(r&1) res += t[--r];
	}
	return res;
}

int main(void){
	FASTIO
	int N;
	cin >> N;
	vector<pair<int,int>> v(N);
	n = 1;
	while(n < N) n <<=1;
	
	for(int i = 0;i<N;i++){
		int k; cin >> k;
		v[i] = {k,i};
		t[i+n] = 1;
	}
	build();
	sort(v.begin(), v.end());
	vector<int> c(N);
	for(int i = 0; i<N;i++){
		int a,b; tie(a,b) = v[i];
		c[b] = i;
	}
	ll ans = 0;
	for(int i = 0;i<N;i++){
		
		ans += query(0, c[i]);
		update(c[i], 0);
	}
	cout << ans <<"\n";
}

BOJ 3006 - 터보 소트

  • 세그먼트 트리
  • 버블 소트 문제와 비슷하지만, 1 -> N -> 2 -> N-2 … 식으로 지나갈때 하나는 [0, ~) , 그리고 [ ~+1, N) 의 쿼리들을 계산 해주면 됨
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

ll t[4040400];
ll n;

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = t[i<<1] + t[i<<1 | 1];
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>1; p>>= 1) t[p>>1] = t[p] + t[p^1];
}

ll query(int l, int r){
	ll res = 0;
	for(l += n, r+= n;  l<r;l>>=1, r>>= 1){
		if(l&1) res += t[l++];
		if(r&1) res += t[--r];
	}
	return res;
}

int main(void){
	FASTIO
	int N;
	cin >> N;
	vector<int > c(N);
	n = 1;
	while(n < N) n <<=1;
	
	for(int i = 0;i<N;i++){
		int k; cin >> k;
		c[k-1] = i;
		t[i+n] = 1;
	}
	build();
	for(int i = 0, j = N-1; i<=j; i++, j--){
		cout << query(0, c[i]) <<"\n";
		update(c[i],0);
		if(i !=j){
			cout <<  query(c[j]+1, N) <<"\n";
			update(c[j], 0);
		}

	}
	
}

BOJ 2243 - 사탕상자

  • 이분탐색으로 순위에 맞는 사탕을 찾아야함
  • 이분탐색중, 해당 구간의 최댓값이 순위보다 작은경우, 순위- 구간 최댓값을 탐색에 넣어야함
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

ll t[3030300];
ll n;

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = t[i<<1] + t[i<<1 | 1];
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>1; p>>= 1) t[p>>1] = t[p] + t[p^1];
}

int q(int node, int k ){
	if(node >= n) return node - n;
	if(t[node*2] >= k) return q(node *2 , k);
	else return q(node*2 +1 , k - t[node*2]);
}

int main(void){
	FASTIO
	int N,M;
	cin >> M;
	N = 1000001;
	n = 1;
	while(n < N) n <<=1;
	
	while(M--){
		int a,b,c, tmp;
		cin >> a;
		if(a == 1){
			cin >> b;
			cout << (tmp = q(1,b))<<"\n";
			update(tmp, t[tmp+n] -1);			
		}
		else{
			cin >> b >> c;
			update(b,t[b+n]+c);
		}
	}
}

BOJ 7578 - 공장

  • Inversion counting 문제 ```cpp #include <bits/stdc++.h> #define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false); using namespace std;

typedef long long ll;

ll t[4040400]; ll n;

void build(){ for(ll i = n-1;i>0;–i ) t[i] = t[i«1] + t[i«1 | 1]; } void update(ll p, ll v){ for(t[p+=n] = v; p>1; p»= 1) t[p»1] = t[p] + t[p^1]; }

ll query(int l, int r){ ll res = 0; for(l += n, r+= n; l<r;l»=1, r»= 1){ if(l&1) res += t[l++]; if(r&1) res += t[–r]; } return res; }

int main(void){ FASTIO int N; cin » N; vector tmp(5050500), arr(5050500); n = 1; while(n < N) n <<=1;

for(int i = 0;i<N;i++){
	int k; cin >> k;
	tmp[k] = i;
}
for(int i = 0;i<N;i++){
	int k; cin >> k;
	arr[tmp[k]]  = i;
}
build();
ll ans = 0;
for(int i = 0;i<N;i++){
	
	ans += query(arr[i]+1, N);
	update(arr[i], 1);
}
cout << ans <<"\n"; } ```

2024년 5월 문제풀이 #2 (최근 수정 : 2024.05.10)

KMP algorithm

https://justicehui.github.io/hard-algorithm/2019/01/16/KMP/ 참고

문자열 매칭 알고리즘을 $O(SP)$ 보다 빠르게 풀수 있는 알고리즘

  • PI[i] : 0~i 까지 문자열의 preffix와 suffix의 공통되는 길이의 max 를 저장

  • 문자열 S와 P의 공통되는 부분까지 가다가 만약, 다른 부분이 생기면 체인을 이용하여 S[i]와 P[i]가 같아질때까지 공통구간의 길이로 줄여나감

BOJ 1786 - 찾기

  • kmp
  • 공백이 있는 문자열이 들어오므로 getline() 이용
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

vector<string> prefix(string str){
    int n = str.size();
    vector<string> p;
    string tmp = ""; tmp+= str[0];
    p.push_back(tmp);
    for(int i = 1; i<n;i++){
        p.push_back(p.back() + str[i]);
    }
    return p;
}
vector<string> suffix(string str){
    int n = str.size();
    vector<string> s;
    string tmp = ""; tmp+= str.back();
    s.push_back(tmp);
    for(int i = n-2; i>=0;i--){
        s.push_back(str[i] + s.back());
    }
    return s;
}

vector<int> pi(string str){
    int n = str.size();
    vector<int> p(n);
    
    p[0] = 0;
    int j = 0;
    for(int i = 1; i<n;i++){
        while(j > 0 && str[i] != str[j]) j = p[j-1];
        if(str[i] == str[j]){
            p[i] = j+1;
            j++;
        }
    }
    return p;
}

vector<int> kmp (string s, string p){
    vector<int> ans;
    int n = s.size(), m = p.size();
    vector<int> PI = pi(p);
    int j = 0;
    for(int i = 0; i<n;i++){
        while(j > 0 && s[i]!= p[j]) j = PI[j-1];
        if(s[i] == p[j]){
            if(j == m-1){
                ans.push_back(i-m+1);
                j = PI[j];
            }
            else j++;
        }
    }
    return  ans;
}

int main(void){
    string s,p;
    
    getline(cin, s);
    getline(cin, p);
    
    vector<int> ans = kmp(s,p);
    
    cout << ans .size()<<"\n";
    for(auto i : ans) cout << (i+1) <<" ";
}

BOJ 1305 - 광고

  • PI 배열 응용
  • 순간 전광판 : 광고 + 광고 접미사 –> 전광판 길이 - PI[전광판 길이 - 1]
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

vector<string> prefix(string str){
    int n = str.size();
    vector<string> p;
    string tmp = ""; tmp+= str[0];
    p.push_back(tmp);
    for(int i = 1; i<n;i++){
        p.push_back(p.back() + str[i]);
    }
    return p;
}
vector<string> suffix(string str){
    int n = str.size();
    vector<string> s;
    string tmp = ""; tmp+= str.back();
    s.push_back(tmp);
    for(int i = n-2; i>=0;i--){
        s.push_back(str[i] + s.back());
    }
    return s;
}

vector<int> pi(string str){
    int n = str.size();
    vector<int> p(n);
    
    p[0] = 0;
    int j = 0;
    for(int i = 1; i<n;i++){
        while(j > 0 && str[i] != str[j]) j = p[j-1];
        if(str[i] == str[j]){
            p[i] = j+1;
            j++;
        }
    }
    return p;
}

vector<int> kmp (string s, string p){
    vector<int> ans;
    int n = s.size(), m = p.size();
    vector<int> PI = pi(p);
    int j = 0;
    for(int i = 0; i<n;i++){
        while(j > 0 && s[i]!= p[j]) j = PI[j-1];
        if(s[i] == p[j]){
            if(j == m-1){
                ans.push_back(i-m+1);
                j = PI[j];
            }
            else j++;
        }
    }
    return  ans;
}

int main(void){
    int N;
    cin >> N;
    string S; cin >> S;
    
    auto ans = pi(S)[N-1];
    cout << N - ans<<"\n";
}

BOJ 3653 - 영화수집

  • segment tree 문제
  • segment tree에 할당되는 칸을 N+M으로 한후, n+M 부터 1을 넣어준다 (기본)
  • $p[i] : i번째 책이 트리 안에 있는 위치$
  • 이후 query(0 ~ p[i]) 으로 p[i]보다 앞에 있는 1의 합을 구해준다.
  • update(p[i], 0), update(M–, 1) : p[i]의 책을 M 위치로 보내준다.
  • 위 과정을 하면 보다 쉽게 책의 위치가 바뀐것을 알수 있다.
  • p[i] = M
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

ll t[4040400];
int p[101010];
ll n;

void build(){
	for(ll i = n-1;i>0;--i ) t[i] = t[i<<1] + t[i<<1 | 1];
}
void update(ll p, ll v){
	for(t[p+=n] = v; p>1; p>>= 1) t[p>>1] = t[p] + t[p^1];
}

ll query(int l, int r){
	ll res = 0;
	for(l += n, r+= n;  l<r;l>>=1, r>>= 1){
		if(l&1) res += t[l++];
		if(r&1) res += t[--r];
	}
	return res;
}


int main(void){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int TC; cin >> TC;
	while(TC--){
		memset(t, 0, sizeof(t));
		memset(p, 0 , sizeof(p));
		int N, M;
		cin >> N >> M;
		n = 1;
		while(n < N+M) n <<=1;
		for(int i = 0;i<N;i++){
			t[i+n+M] = 1;
			p[i+1] = i+M;
		}
		
		build();
		
		while(M--){
			int tmp; cin >> tmp;
			
			cout << query(0, p[tmp])<< " ";
			update(p[tmp],0);
			update(M-1, 1);
			p[tmp] = M-1;
		}
		cout << "\n";
	}
}

BOJ 12851 - 숨바꼭질 2

  • 숨바꼭질 1과 같은 문제
  • queue < tuple< int, int ,int » 형태로 현재위치, 카운트, 이전위치를 저장해줌
  • 1에서 출발하는 경우 2배한것과 +1 이 같음 –> vt[cur]로만 처리할 경우, 중복으로 카운트 안됨
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

bool vt[101010];
ll N, K, mn = 1e6+1, ans;

int main(void){
	
	cin >> N >> K;
	
	queue<tuple<ll, ll, ll>> q;
	q.push({N,0,100001});
	
	while(!q.empty()){
		ll cur, t, prv; tie(cur,t, prv) = q.front();
		if(prv != K) vt[prv] = true;
		q.pop();
		if(cur > 100000 || cur < 0 || vt[cur]) continue;
		if(cur == K){
			if(mn > t) ans = 1, mn = t;
			else if(mn == t) ans++;
		}
		
		if(t > mn) break;
		q.push({cur+1, t+1,cur});
		q.push({cur-1, t+1,cur});
		q.push({cur << 1, t+1,cur});
		
	}
	cout << mn << "\n" << ans <<"\n";
}

BOJ 13913 - 숨바꼭질 4

  • 경로 추적 + 숨바꼭질 1
  • 숨바꼭질 2 문제의 코드와 다르게, bfs 를 구성해보았음
  • 다양한 형태의 bfs와 bfs에서 경로추적 코드 기억
#include <bits/stdc++.h>
#define FASTIO cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;

ll N, K;
deque<int> ans;
int d[101010], p[101010];
int main(void){
	FASTIO
	cin >> N >> K;
	
	queue<int> q;

	memset(d, -1, sizeof(d));
	
	d[N] = 0;
	p[N] = N;
	q.push(N);
	
	while(d[K] == -1){
		int cur = q.front(); q.pop();
		for(int nxt : {cur-1, cur+1, cur <<1}){
			if(nxt < 0 || nxt > 100000 || d[nxt] != -1) continue;
			d[nxt] = d[cur]+1;
			p[nxt] = cur;
			q.push(nxt);
		}
	}
	cout << d[K] <<"\n";
	ans.push_front(K);
	
	while(ans.front() != N){
		ans.push_front(p[ans.front()]);
	}
	for(int i : ans) cout << i <<" ";
} 

BOJ 11692 - 시그마 함수

sol. 1 풀이

시간복잡도 : $O(\sqrt(N)M)$ –> TLE

M이 매우 크기 때문에 M에 대해 전체 탐색을 하는것은 잘못된 선택이다

sol. 2 풀이

시간복잡도 : $O(log M)$

$n = 2^x p_{1}^{e_1} p_{2}^{e_2} p_{3}^{e_3} … p_{k}^{e_k}$ 으로 정의할 수 있다. 이때 $p_i$는 홀수이다.

여기서 약수의 합은 다음처럼 구할 수 있다.

$(1+2+2^2 + … + 2^x) (1+p_{1} + p_{1}^2 … p_{1}^{e_1})…(1+p_{k} + p_{k}^2 … p_{k}^{e_k})$ 이때 첫번째 항은 무조건 홀수 이다. 그러므로 나머지 항 중 하나라도 짝수여야 n 이 짝수다.

이 말은 $e_k$ 중 하나라도 홀수면 n 이 짝수라는 뜻이다.

우리는 문제를 풀때 전체 개수에서 홀수인 개수를 빼도 정답을 구할 수 있다.

즉 $e_k$ 가 모두 짝수일 때를 구하면 된다.

근데 $e_k$가 모두 짝수라는건 결국 $n = P^2 2^x$ 와 같다. 이때 $P$는 홀수 이다.

$m \geq P^2 2^x$의 개수를 구해야 한다.

$ m / {2^x} \geq P^2$ 에서 각 $x$마다의 $P$의 개수를 구하면 되므로

홀수 $P$의 개수는 $(\sqrt(m) +1 )/2$와 같다.

즉 $m$을 2로 나누면서 해당 식의 값을 찾아주면 된다.

sol.3 풀이

$O(1)$

위의 수식을 다시 생각해보면 $n = P^2 2^x$ 이다. 근데 x가 홀수일 때, 짝수일 때 생각하면?

$n = X^2 or 2X^2$ 이다. 이때 $X$는 자연수이다. 즉 전체에서 다음 두가지를 만족하는 수의 개수를 빼주면 되므로

$O(1)$에 처리가 가능하다.

#include <bits/stdc++.h>

using namespace std;

int main(void){
    
    long long m;
    cin >> m;
    
    cout << m - (long long)sqrt(m) - (long long)sqrt(m/2);
}