티스토리 뷰
현황은 아래와 같다.
A. 6174
문제에 나온 것을 그대로 구현하면 된다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
int n;
int main(){
scanf("%d", &t);
while(t--){
scanf("%d", &n);
int ans = 0;
while(n != 6174){
string s = to_string(n);
sort(s.begin(), s.end());
n = -atoi(s.c_str());
reverse(s.begin(), s.end());
n += atoi(s.c_str());
ans++;
}
printf("%d\n", ans);
}
}
B. The Game of Efil
브루트포스로 모든 경우를 시도해 보면 된다. 경우의 수가 $2^{nm}$가직이고 한 가지 경우를 시험하는 데 $8nm$번 정도의 시행이 필요하므로 $O(nm 2^{nm})$에 문제를 맞을 수 있다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int xx[]={0, 1, 1, 1, 0, -1, -1, -1}, yy[]={-1, -1, 0, 1, 1, 1, 0, -1};
int n, m, k;
int arr[16][16];
int conf[16][16];
int ans;
void backtrack(int x, int y){
if(x==n){
for(int i=0; i<n; i++){
for(int j=0; j<m; j++){
int cnt = 0;
for(int d=0; d<8; d++){
int ti = (i+n+xx[d])%n, tj = (j+m+yy[d])%m;
if(conf[ti][tj]) cnt++;
}
bool b = (conf[i][j] && 2 <= cnt && cnt <= 3) || (!conf[i][j] && cnt == 3);
if(b != arr[i][j]) return;
}
}
ans++;
return;
}
if(y==m) {backtrack(x+1, 0); return;}
conf[x][y] = 0;
backtrack(x, y+1);
conf[x][y] = 1;
backtrack(x, y+1);
}
int main(){
for(int tc=1; ; tc++){
scanf("%d %d", &n, &m);
if(!n) break;
scanf("%d", &k);
memset(arr, 0, sizeof(arr));
for(int i=1; i<=k; i++){
int x, y;
scanf("%d %d", &x, &y);
arr[x][y] = 1;
}
ans = 0;
backtrack(0, 0);
printf("Case %d: ", tc);
if(!ans) puts("Garden of Eden.");
else printf("%d possible ancestors.\n", ans);
}
}
C. Rising Tides
답에 대한 parametric search를 하고, bfs를 돌리면 된다. 시간 복잡도는 $O(nm \log X)$이다. 답이 시작하는 칸의 높이 이하여야 함에 주의하자.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
const int xx[]={1, 0, -1, 0}, yy[]={0, 1, 0, -1};
int t;
int n, m;
ll arr[502][502];
ll dist[502][502];
int qx[250002], qy[250002], f, b;
inline void push(ll x, ll y){
qx[b] = x, qy[b] = y, b++;
}
bool able(ll LIM){
f=b=0;
for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) dist[i][j] = INF;
dist[1][1] = 0;
push(1, 1);
while(f<b){
int x = qx[f], y = qy[f]; f++;
if(x==n && y==m) return true;
for(int d=0; d<4; d++){
int tx = x+xx[d], ty = y+yy[d];
if(tx < 1 || tx > n || ty < 1 || ty > m) continue;
if(dist[tx][ty] != INF || arr[tx][ty] - (dist[x][y] + 1) < LIM) continue;
dist[tx][ty] = dist[x][y] + 1;
push(tx, ty);
}
}
return false;
}
int main(){
scanf("%d", &t);
while(t--){
scanf("%d %d", &n, &m);
for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) scanf("%lld", &arr[i][j]);
ll L = 1, R = arr[1][1], ANS = 0;
while(L<=R){
ll M = (L+R)/2;
if(able(M)) ANS = M, L = M+1;
else R = M-1;
}
if(!ANS) puts("impossible");
else printf("%lld\n", ANS);
}
}
D. 행운쿠키 제작소
냅색 비슷한 DP를 돌려 주면 된다. $DP[x][a]$는 $1$번부터 $x$번 쿠키까지 어느 오븐에서 구울지를 선택했을 때, 1번 오븐에서 구워지는 시간의 총합이 $a$라면 2번 오븐에서 구워지는 시간의 최솟값이 얼마인지를 저장해 놓는다. 이 DP는 $O(100 n^2)$, 토글링을 하면 메모리 $O(100n)$에 전이할 수 있다. 이제 $max(DP[x][a], a)$의 최솟값이 답이 된다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 1e9;
int t;
int n;
int a[1002], b[1002], w[1002];
int DP[2][100002];
int main(){
scanf("%d", &t);
while(t--){
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d %d", &a[i], &b[i]);
for(int i=0; i<=n*100; i++) DP[0][i] = DP[1][i] = INF;
DP[0][0] = 0;
for(int i=1; i<=n; i++){
int d = i%2;
for(int j=0; j<=n*100; j++) DP[d][j] = min(DP[!d][j] + b[i], j >= a[i] ? DP[!d][j-a[i]] : INF);
}
int ans = INF;
for(int i=0; i<=n*100; i++) ans = min(ans, max(i, DP[n&1][i]));
printf("%d\n", ans);
}
}
E. 숫자 카드 제거 게임
이 상태로 생각하면 조금 힘들다. 결국 중요한 건 인접한 수의 개수인데, 초기에는 $N$개의 수가 인접하다. 여기서 수 하나를 제거하면 인접한 수의 덩어리가 하나 또는 둘로 쪼개지게 된다. 따라서 한 덩어리의 길이를 해당 덩어리에 있는 수로 정의하면, 길이의 집합으로 게임의 상태를 정의할 수 있다. 게임판이 둘로 나눠지는 것은 sprague-grundy를 쓰면 계산할 수 있어 보인다.
이제 sprague-grundy를 실제로 돌려 보면 초반부를 제외하고 $34$ 주기의 사이클이 생기는 것을 확인할 수 있다. 이제 이 부분을 하드코딩해서 정답을 받을 수 있다.
이 문제는 코드가 정답과도 같으므로 코드를 첨부하지 않는다.
F. 바벨탑의 저주
어떤 정점 $x$가 저주 노드일 조건은 다음과 같다.
- 정점 $x$가 리프 노드이거나,
- 정점 $x$의 자식 노드 $c_1, \cdots, c_k$에 대해 ($k \ge 1$) 자식 $c_i$의 서브트리를 뒤집은 트리가 자식 $c_{k+1-i}$의 트리와 같다.
해싱을 통해 두 트리가 같은지 확인하는 방법을 알아보자. 두 트리가 같은지의 여부는 다음과 같이 나누어 생각하는 것이 편리하다.
- 두 트리의 구조가 같은가?
- 구조가 같다면, 각 노드에 쓰인 수가 같은가?
먼저 구조부터 생각해 보자. 모든 트리의 구조는 괄호 문자열로 나타낼 수 있다. 원래대로라면 tree isomorphism까지 생각해야 하지만, 이 경우 자식들의 순서가 정해져 있으므로 논의가 더 쉬워진다. 다음과 같은 구조를 따른다.
- 리프 노드는
()
으로 표현한다. - 어떤 노드 $x$의 자식이 차례대로 $c_1, \cdots, c_k$이고, 이들의 서브트리의 representation이 각각 $f(c_1), \cdots, f(c_k)$라면, $x$의 representation은 $f(c_1) + \cdots + f(c_k)$를 괄호로 둘러싼 문자열로 표기한다.
- 예를 들어, $k = 2$, $f(c_1)$이
(())
, $f(c_2)$가(()(()))
라면, $f(x)$는((())(()(())))
이 된다.
- 예를 들어, $k = 2$, $f(c_1)$이
괄호 문자열은 이진 수열로도 볼 수 있으므로, 해당 문자열을 해싱해 저장하면 된다. 나는 (
를 $1$, )
를 $2$로 계산해 3진수로 인코딩했다.
수를 인코딩하는 것은 더 쉽게 가능하다. 괄호 문자열까지 가지 않아도 되고, 그냥 보는 순서대로 차례로 인코딩하면 된다. 이제 이 값들을 전부 비교해 주면 문제를 해결할 수 있다.
최초에 child
배열을 정렬해 주는 것을 잊지 말도록 하자.
#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
//const ll MOD = 1'000'000'009, MOD2 = 1'300'000'003;
const ll MOD = 1000000000000000003, MOD2 = 1300000000000000027;
const ll MULT = 500'029, MULT2 = 500'041;
int n;
ll p3[1000002], pmult[1000002], pmult2[1000002];
ll arr[500002];
int par[500002], sz[500002];
vector<int> child[500002];
vector<int> ans;
ll str1[500002], str2[500002]; /// 앞에서부터, 뒤에서부터 구조 해싱. (: 1, ): 2
ll val1[500002], val2[500002];
ll valr1[500002], valr2[500002];
void dfs(int x){
sz[x] = 1;
for(int y: child[x]){
dfs(y);
sz[x] += sz[y];
}
/// str1, str2
str1[x] = (p3[sz[x]*2-1] + 2) % MOD;
int pos = sz[x]*2-1;
for(int i=0; i<(int)child[x].size(); i++){
int y = child[x][i];
pos -= sz[y] * 2;
str1[x] = (str1[x] + str1[y] * p3[pos]) % MOD;
}
str2[x] = (p3[sz[x]*2-1] + 2) % MOD;
pos = sz[x]*2-1;
for(int i=(int)child[x].size()-1; i>=0; i--){
int y = child[x][i];
pos -= sz[y] * 2;
str2[x] = (str2[x] + str2[y] * p3[pos]) % MOD;
}
/// val1, val2
val1[x] = pmult[sz[x]-1] * arr[x] % MOD;
pos = sz[x]-1;
for(int i=0; i<(int)child[x].size(); i++){
int y = child[x][i];
pos -= sz[y];
val1[x] = (val1[x] + val1[y] * pmult[pos]) % MOD;
}
val2[x] = pmult[sz[x]-1] * arr[x] % MOD;
pos = sz[x]-1;
for(int i=(int)child[x].size()-1; i>=0; i--){
int y = child[x][i];
pos -= sz[y];
val2[x] = (val2[x] + val2[y] * pmult[pos]) % MOD;
}
valr1[x] = pmult2[sz[x]-1] * arr[x] % MOD2;
pos = sz[x]-1;
for(int i=0; i<(int)child[x].size(); i++){
int y = child[x][i];
pos -= sz[y];
valr1[x] = (valr1[x] + valr1[y] * pmult2[pos]) % MOD2;
}
valr2[x] = pmult2[sz[x]-1] * arr[x] % MOD2;
pos = sz[x]-1;
for(int i=(int)child[x].size()-1; i>=0; i--){
int y = child[x][i];
pos -= sz[y];
valr2[x] = (valr2[x] + valr2[y] * pmult2[pos]) % MOD2;
}
/// 답 갱신
if(str1[x] == str2[x] && val1[x] == val2[x] && valr1[x] == valr2[x]) ans.push_back(x);
}
int main(){
p3[0] = pmult[0] = pmult2[0] = 1;
for(int i=1; i<=1'000'000; i++){
p3[i] = p3[i-1] * 3 % MOD;
pmult[i] = pmult[i-1] * MULT % MOD;
pmult2[i] = pmult2[i-1] * MULT2 % MOD2;
}
scanf("%d", &n);
for(int i=1; i<=n; i++){
int x;
scanf("%d", &x);
arr[i] = x;
}
vector<ll> renumber (arr+1, arr+n+1);
sort(renumber.begin(), renumber.end());
renumber.erase(unique(renumber.begin(), renumber.end()), renumber.end());
for(int i=1; i<=n; i++){
arr[i] = lower_bound(renumber.begin(), renumber.end(), arr[i]) - renumber.begin() + 1;
}
for(int i=1; i<n; i++){
int p, x;
scanf("%d %d", &p, &x);
par[x] = p, child[p].push_back(x);
}
for(int i=1; i<=n; i++) sort(child[i].begin(), child[i].end());
dfs(1);
sort(ans.begin(), ans.end());
printf("%d\n", (int)ans.size());
for(int x: ans) printf("%d ", x);
}
G. 초콜릿 비긴즈
조금 생각해 봤는데 구현이 복잡할 것 같아서 리롤했다.
G. 이상한 판 뒤집기 게임
리롤했는데 티어가 오른 경우는 이게 처음이었다. 꽤 고민해 봤는데 잘 모르겠어서 다시 리롤했다.
G. 원형 게임
Lazy Propagation Segment Tree를 이용하는 문제이다. 구현이 꽤나 복잡하다.
세그먼트 트리의 각 노드에 diff, lval, rval을 저장한다. 이들은 각각 구간 내의 최대 인접한 수 차이, 가장 왼쪽 수, 가장 오른쪽 수이다. 이렇게 저장해 두면 구간 내에서 인접한 수의 최대 차이를 계속 구할 수 있다.
그러나 업데이트가 들어올 수 있기 때문에 정보를 추가로 더 저장해야 한다. 위 diff, lval, rval을 총 네 개씩 저장할 것이다. 어떤 상태 $d$ ($0 \le d \le 3$)을 다음과 같이 정의하자.
- $d=0$: 모든 참가자가 참여하지 않는다.
- $d=1$: 현재 상태.
- $d=2$: 현재 참가자와 참가하지 않는 사람이 서로 뒤바뀐다.
- $d = 3$: 모든 사람이 참가한다.
이 네 경우에 대해 계속 diff, lval, rval 값을 관리하고 있으면 업데이트 쿼리가 주어졌을 때 이 값들을 모두 변경해줄 수 있다. 쿼리가 들어올 때는 $\text{diff}[1]$과 $abs(\text{lval}[1] - \text{rval}[1])$ 중 더 큰 값을 반환하면 된다. 시간 복잡도는 $O(Q \log N)$이다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int graph[4][4] = {
0, 0, 3, 3,
0, 1, 2, 3,
0, 2, 1, 3,
0, 3, 0, 3
};
inline int add(int a, int b){
return (b==0 || b==3) ? b : (b==1 ? a : (a^3));
}
struct segTree{
struct Node{
int diff[4], lval[4], rval[4]; /// [00], [01], [10], [11] -> [0 들어오면], [현재], [거꾸로], [1 들어오면]
Node(){}
Node(int v){
lval[0] = lval[2] = rval[0] = rval[2] = 0;
diff[0] = diff[1] = diff[2] = diff[3] = 0;
lval[1] = lval[3] = rval[1] = rval[3] = v;
}
Node operator+(const Node &r)const{
Node ret;
for(int d=0; d<4; d++){
ret.diff[d] = max({diff[d], r.diff[d], (rval[d] && r.lval[d] ? abs(r.lval[d] - rval[d]) : 0)});
ret.lval[d] = lval[d] ? lval[d] : r.lval[d];
ret.rval[d] = r.rval[d] ? r.rval[d] : rval[d];
}
return ret;
}
void reflect(int l){
if(!l){
diff[1] = diff[0], lval[1] = lval[0], rval[1] = rval[0];
diff[2] = diff[3], lval[2] = lval[3], rval[2] = rval[3];
}
else if(l==3){
diff[1] = diff[3], lval[1] = lval[3], rval[1] = rval[3];
diff[2] = diff[0], lval[2] = lval[0], rval[2] = rval[0];
}
else{ /// l=2
swap(diff[1], diff[2]);
swap(lval[1], lval[2]);
swap(rval[1], rval[2]);
}
}
} tree[1<<19];
int lazy[1<<19];
void init(int i, int l, int r, int* arr){
lazy[i] = 1;
if(l==r){
tree[i] = Node(arr[l]);
return;
}
int m = (l+r)>>1;
init(i*2, l, m, arr);
init(i*2+1, m+1, r, arr);
tree[i] = tree[i*2] + tree[i*2+1];
}
void propagate(int i, int l, int r){
if(lazy[i] == 1) return;
tree[i].reflect(lazy[i]);
if(l!=r){
lazy[i*2] = graph[lazy[i*2]][lazy[i]];
lazy[i*2+1] = graph[lazy[i*2+1]][lazy[i]];
}
lazy[i] = 1;
}
void update(int i, int l, int r, int s, int e, int v){
propagate(i, l, r);
if(r<s || e<l) return;
if(s<=l && r<=e){
lazy[i] = v;
propagate(i, l, r);
return;
}
int m = (l+r)>>1;
update(i*2, l, m, s, e, v), update(i*2+1, m+1, r, s, e, v);
tree[i] = tree[i*2] + tree[i*2+1];
}
int query(int i, int l, int r){
propagate(i, l, r);
return max(abs(tree[i].lval[1]-tree[i].rval[1]), tree[i].diff[1]);
}
} tree;
int n, q;
int arr[200002];
int main(){
scanf("%d %d", &n, &q);
for(int i=1; i<=n; i++) scanf("%d", &arr[i]);
tree.init(1, 1, n, arr);
printf("%d\n", tree.query(1, 1, n));
while(q--){
int qx, ql, qr;
scanf("%d %d %d", &qx, &ql, &qr);
qx = vector<int> {-1, 0, 3, 2} [qx];
if(ql <= qr) tree.update(1, 1, n, ql, qr, qx);
else{
tree.update(1, 1, n, 1, qr, qx);
tree.update(1, 1, n, ql, n, qx);
}
printf("%d\n", tree.query(1, 1, n));
}
}
H. 소수 피하기
옛날 아레나에 참가했을 때 못 푼 문제였고 다시 풀어봐야겠다고 생각만 하고 있었는데 못 풀고 있었다.
지금 리롤하면 영원히 업솔빙을 안 할 것 같아서
결국 에디토리얼을 보고 풀었다.
#include <bits/stdc++.h>
#include "atcoder/maxflow"
using namespace std;
using namespace atcoder;
typedef long long ll;
const int MX = 2'000'000;
const ll INF = 1e18;
int n, E, O;
bool isPrime[2000002];
int arr[205], even[205], odd[205];
bool isEven[205];
int main(){
fill(isPrime+2, isPrime+MX+1, true);
for(int i=2; i*i<=MX; i++){
if(!isPrime[i]) continue;
for(int j=i*i; j<=MX; j+=i) isPrime[j] = 0;
}
scanf("%d", &n);
for(int i=1; i<=n; i++){
scanf("%d", &arr[i]);
if(arr[i] % 2) odd[i] = arr[i], even[i] = arr[i] + 1;
else even[i] = arr[i], odd[i] = arr[i] + 1;
}
E = n+1, O = n+2;
mf_graph<ll> graph(n+3);
for(int i=1; i<=n; i++){
if(arr[i] % 2) graph.add_edge(i, O, 1);
else graph.add_edge(E, i, 1);
}
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(i==j) continue;
if(isPrime[even[i] + odd[j]]) graph.add_edge(i, j, INF);
}
}
if(count(arr+1, arr+n+1, 1) >= 2){
for(int i=1; i<=n; i++){
if(arr[i] == 1) graph.add_edge(E, i, INF);
}
}
int flow = graph.flow(E, O);
vector<bool> vec = graph.min_cut(E);
// for(bool x: vec) printf("%d ", x);
// puts("");
for(int i=1; i<=n; i++){
isEven[i] = vec[i];
}
vector<int> ans;
for(int i=1; i<=n; i++) if(isEven[i] != (arr[i] % 2 == 0)) ans.push_back(i);
printf("%d\n", (int)ans.size());
for(int x: ans) printf("%d ", x);
}
'문제풀이 > 랜덤 마라톤' 카테고리의 다른 글
랜덤 마라톤 10주차 (0) | 2024.08.13 |
---|---|
랜덤 마라톤 9주차 (0) | 2024.08.06 |
랜덤 마라톤 7주차 (0) | 2024.07.24 |
랜덤 마라톤 6주차 (0) | 2024.07.14 |
랜덤 마라톤 5주차 (0) | 2024.07.06 |