본문 바로가기
PS/AtCoder

AtCoder ABC 263 풀이

by jaehoonChoi 2022. 8. 28.

https://atcoder.jp/contests/abc263/tasks

 

Tasks - LINE Verda Programming Contest(AtCoder Beginner Contest 263)

AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

atcoder.jp

 

A.

정렬후 케이스를 잘 나눠주면 됩니다.

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

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    int a[5];
    for (int i = 0; i < 5; i++)cin >> a[i];
    sort(a, a + 5);
    int cnt = 0;
    if (a[0] == a[1] && a[1] != a[2] && a[2] == a[4]) {
        cout << "Yes";
    }
    else if (a[0] == a[2] && a[2] !=a[3] && a[3] == a[4]) {
        cout << "Yes";
    }
    else {
        cout << "No";
    }
}

 

B.

트리 깊이를 찾아주면 됩니다. dfs로 해도 되고 dp식으로 간단히 풀 수 있습니다. 

 

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

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    int n; cin >> n;
    vector<int>p(n+1);
    for (int i = 2; i <= n; i++)cin >> p[i];
    vector<int>dep(n+1, 0);
    for (int i = 2; i <= n; i++) {
        dep[i] = dep[p[i]] + 1;
    }
    cout << dep[n];
}

 

C.

N과 M 시리즈 문제입니다. 그냥 백트래킹 해주면 됩니다. 

 

#include<bits/stdc++.h>
using namespace std;
int n, m, vis[11];

void dfs(int idx, int cnt) {
    if (cnt == n) {
        for (int i = 1; i <= m; i++) {
            if (vis[i]) cout << i << " ";
        }
        cout << '\n';
    }
    for (int i = idx; i <= m; i++) {
        if (vis[i])continue;
        vis[i] = 1;
        dfs(i, cnt + 1);
        vis[i] = 0;
    }
}

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

 

 

D. 

왼쪽부터 L로 변경, 오른쪽부터 R로 변경 가능할때 합의 최솟값을 찾는 문제입니다. 

어차피 두 경로가 겹치면 나중에 변경한 값으로 덮이니 겹치지 않는 경우와 같게 볼 수 있습니다. 

왼쪽부터 i칸까지, 오른쪽부터 j개 칸을 덮은 값을 구해보면, 

L*i+R*j+psum[N-j]-psum[i] 입니다. N-j=k라고 놓으면, (단, k>i) 

식은 (psum[k]-kR)-(psum[i]-Li)+NR이 됩니다. 

psum[i]-Pi=rp[i], psum[i]-Li=lp[i]로 정의합시다. 

식은 rp[k]-lp[i]+상수 로 깔끔해졌습니다. 여기서 잘 알려진 테크닉을 사용합니다. 

lp[i]만 최댓값을 갱신하며 현재 인덱스의 rp[i]만 빼주면서 차이도 같이 갱신을 해주면 됩니다. 

O(N)에 해줄 수 있습니다. 

 

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAX = 200005;
ll n, l, r, a;
ll psum[MAX], lp[MAX], rp[MAX];

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n >> l >> r;
    for (int i = 1; i <= n; i++) {
        cin >> a;
        psum[i] = psum[i - 1] + a;
        lp[i] = psum[i] - l * i;
        rp[i] = psum[i] - r * i;
    }
    ll ans = psum[n];
    ll maxi = 0;
    for (int i = 0; i <= n; i++) {
        maxi = max(maxi, lp[i]);
        ans = min(ans, rp[i] - maxi + n * r);
    }
    cout << ans;
}

 

 

E.

기댓값 DP 문제입니다. DP[i]를 i번에서 시작하여 N번에 도달할때까지 이동횟수의 기댓값이라 정의합니다. 

i=N-1부터 순차적으로 구해나갑니다. (i=1부터 하지 않는 이유는 i=N일땐 주사위가 없어서

식이 달라지므로 귀찮습니다.)

DP[i]는 DP[i]부터 DP[i+a[i]]까지에서 왔습니다. 기댓값은 평균값과 같으므로, 

DP[i]=sum(DP[j]+1)/(a[i]+1) 입니다. (DP[j]+1인 이유는 DP[j]에서 1번 이동해서 DP[i]로 오기 때문)

식을 정리해주면, 양변에 모두 DP[i]가 있으므로, DP[i]만 몰아서 정리하면, 식이 나오고 

구현해주면 됩니다. 여기서 sum(DP[j])부분은 누적합으로 관리해서 O(1)에 처리해야 O(N)이 됩니다. 

마지막으로 구하는게 P/Q로 나타낼 시 PQ^-1 (mod p)를 구하라고 하네요. 

p가 소수이므로 페르마 소정리+분할정복으로 P/Q 를 PQ^(p-2)로 바꿔서 계산해주면 됩니다. 

 

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 998244353;
ll n, a[200005], dp[200005], psum[200005];

ll pow(ll a, ll b) {
    ll ret = 1;
    while (b) {
        if (b & 1) ret = (ret * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return ret;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n;
    for (int i = 1; i < n; i++)cin >> a[i];
    for (int i = n - 1; i; i--) {
        int s = (psum[i + 1] - psum[i + a[i] + 1] + mod) % mod;
        dp[i] = (s + a[i] + 1) * pow(a[i], mod - 2);
        dp[i] %= mod;
        psum[i] = dp[i] + psum[i + 1];
        psum[i] %= mod;
    }
    cout << dp[1];
}

 

 

 

F.

풀이 예정

 

 

 

G. 

플로우 냄새가 납니다. 백준 소수쌍 문제(https://www.acmicpc.net/problem/1017)와 너무나 닮았는데

짜증나는게 1의 존재입니다.  1만 없다면 이분그래프로 홀짝을 나누고 최대유량문제가 됩니다. 

1은 자신끼리 2를 만들수 있어서 까다롭습니다. 1을 어떻게 써야할까요?

우선 1 2개로는 무조건 답이 1 증가합니다. 그러나 1을 다른 짝수들과 연결가능하다면

1 1개로도 답이 1이상 증가하게 됩니다. 따라서 우선 1을 다른 짝수들과 연결시켜 최대유량을 구해주고, 

나머지 1들을 2개씩 묶어주는게 최적해임을 알 수 있습니다. 

1과 소수가 되는 짝수들을 연결시키고 최대유량 알고리즘을 1번 더 돌려줍니다.

이 때 나오는건 이미 홀짝 이분그래프로 최대유량을 흘린 뒤 1로 만드는 최대유량이 됩니다.

답은 r1(1아닌것끼리 최대유량)+use(1과 짝수들의 최대유량)+(cnt-use)/2 (나머지 1들을 묶은 개수) 입니다. 

풀면서 느껴지는게 앳코더 문제들이 교육적이고 좋은 문제들이 많은 것 같네요.

 

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAX = 20000007;
ll n, a[111], b[111];
vector<int>g[111];
ll work[111], lv[111];
ll cap[111][111], flow[111][111];

// prime test
bool prime(int x) {
	for (int i = 2; i <= x / i; i++) {
		if (!(x % i)) return 0;
	}
	return 1;
}

// Dinic 
struct Dinic {
	void add(int u, int v, ll c) {
		g[u].push_back(v);
		g[v].push_back(u);
		cap[u][v] += c;
	}
	bool bfs(int S, int T) {
		memset(lv, -1, sizeof(lv));
		queue<int>q;
		lv[S] = 0;
		q.push(S);
		while (q.size()) {
			int cur = q.front(); q.pop();
			for (int nxt : g[cur]) {
				if (lv[nxt] == -1 && cap[cur][nxt] > flow[cur][nxt]) {
					lv[nxt] = lv[cur] + 1;
					q.push(nxt);
				}
			}
		}
		return ~lv[T];
	}
	ll dfs(int cur, ll w, int T) {
		if (cur == T) return w;
		for (ll& i = work[cur]; i < (ll)(g[cur].size()); i++) {
			int nxt = g[cur][i];
			if (lv[nxt] == lv[cur] + 1 && cap[cur][nxt] > flow[cur][nxt]) {
				ll f = dfs(nxt, min(w, cap[cur][nxt] - flow[cur][nxt]), T);
				if (f) {
					flow[cur][nxt] += f;
					flow[nxt][cur] -= f;
					return f;
				}
			}
		}
		return 0;
	}
	ll max_flow(int S, int T) {
		ll ret = 0;
		while (bfs(S, T)) {
			memset(work, 0, sizeof(work));
			while (1) {
				ll f = dfs(S, 1e18, T);
				if (!f) break;
				ret += f;
			}
		}
		return ret;
	}
}dnc;


int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n;
	int S = 0, T = n + 1;
	int idx = -1;
	ll cnt = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i] >> b[i];
		if (a[i] == 1) {
			idx = i;
			cnt = b[i];
		}
		// 1아닌 것들끼리 이분그래프 형성
		// 왼쪽이 홀수, 오른쪽이 짝수 
		else {
			if (a[i] & 1)dnc.add(S, i, b[i]); 
			else dnc.add(i, T, b[i]); 
		}
    }
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (a[i] == 1 || a[j] == 1) continue;
			// 홀 -> 짝 연결 
			if ((a[i] & 1) && prime(a[i] + a[j]) && !(a[j] & 1)) {
				dnc.add(i, j, 1e18);
			}
		}
	}
	ll ret1 = dnc.max_flow(S, T); 
	// 1이 없으면
	if (idx == -1) {
		cout << ret1;
		return 0;
	}
	// 1을 연결시키고 최대유량 1번 더 흘림 
	dnc.add(S, idx, cnt);
	for (int i = 1; i <= n; i++) {
		if (prime(1 + a[i])) {
			dnc.add(idx, i, 1e18);
		}
	}
	ll use = dnc.max_flow(S, T);
	cout << ret1 + use + (cnt - use) / 2;
}

 

 

 

 

'PS > AtCoder' 카테고리의 다른 글

AtCoder ABC 261 풀이  (0) 2022.09.01
AtCoder ABC 262 풀이  (0) 2022.08.30
AtCoder ABC 266 풀이  (0) 2022.08.29
AtCoder ABC 264 풀이  (0) 2022.08.27
AtCoder ABC 265 풀이  (0) 2022.08.25

댓글