본문 바로가기

PS/Competition

SCPC 2021 1차 예선 후기

반응형

7/16 15:00 ~ 7/17 15:00에 진행되었던 SCPC 2021 1차 예선에 참여해보았습니다.

혹여라도 풀이 글이 문제가 될 경우 바로 비공개 처리 하도록 하겠습니다.

 

 

후반 문제 난이도는 대강 골드 상위? 플레 중하위권? 쯤 될 것 같네요.

작년에는 codeground에서 진행되고 있는지도 모르고 내내 자버려서 참여를 못했기에

예년과의 난이도 비교는 할 수 없네요 ㅠㅠ

올해에는 여러 문제에서 삽질을 심하게 하는 바람에 많이 틀렸고 많이 제출하였습니다.

 

2차 예선 잘 되어 본선까지 나갈 수 있으면 좋을 것 같네요

 

문제는 아마 추후에 codeground에서 보실 수 있을 것 같습니다.

 

1. 친구들

문제 요약

n명의 사람이 있고 각각 1~n의 번호가 붙어있다.

이 사람들은 Di (0<Di<=n)의 값을 지니는데 이는 i+Di번째 사람과 친구라는 소리다.

i+Di>n이면 그냥 뒤에는 친구가 없다는 말

1<=T<=50 (테스트 케이스 수)

n<=100000

 

풀이

사람들은 모두 친구를 가지고 있다 i+Di이내에 친구가 있으면 그 사람이 이 친구체인의 마지막 사람일 것이다.

즉 i+Di가 n을 넘어서면 그 뒤에 친구가 없고 해당 i번째 사람이 마지막 친구다. <- 이런 사람 수를 세어준다

 

소스코드

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

int main(){
	int T, n, N;
	scanf("%d",&T);
	for(int N=0; N<T; ){
		int a, c=0;
		scanf("%d",&n);
		for(int i=0; i<n; ++i){
			scanf("%d",&a);
			if(i+a>=n) ++c;
		}
		printf("Case #%d\n%d\n",++N,c);
	}
}

 

2. 이진수

문제 요약

n개 비트로 구성된 A=a1a2,,,an에 대해 자연수 t가 주어질 때 아래의 연산을 통해 n개 비트로 구성된 이진수 B를 구한다.

1. 처음에 B는 0

2. if i-t>0 && a(i-t) ==1 : b(i)=1

3. if i+t<=n && a(i+t)==1 : b(i)=1

만들어진 B와 t가 주어질 때 가능한 A중 이진수로 보았을 때 가장 작은 값을 구해라

1<=T<=64 (테스트 케이스)

2<=n<=50000, 1<=t<n

 

풀이

이진수로 보았을때 가장 작은 값이 오른쪽이 최소화되어야한다고 생각해서 삽질을 심하게 하다가

마지막에 그 반대인걸 알게되어 바로 맞았다. (내 3시간...)

 

범위가 허용하는 한, bi기준 -t, +t좌표중 하나라도 a가 0이면 bi는 1이다.

또 bi가 0이라는 말은 -t, +t좌표의 a가 모두 0이어야 한다.

사전식 가장 작은 수를 구하는거로 보면 되므로 일단 그리디라 할 수 있다.

왼쪽이 최대한 0이 될 수 있도록 해주면 된다.

우선 전처리를 bi가 0인 경우 a(i-t) = a(i+t) = 0으로 고정한다.

두번째로, for i=1~n : if b(i-t)==1 : a(i)=1, b(i-t) = b(i+t) = 0.

 bi-t가 1인게 발견되면 바로 ai=1로 둔다. 현재까지 A에서 1로 두기위해 가능한 부분 중 가장 작은 수가 되기 때문.

 ai가 1이 되었다는 말은 b(i+t)가 1인걸 체크했다는 소리다. 즉, a(i+2t)에서 b(i+t)에 대해 신경쓸필요가 없어야 한다.

 그래서 b(i+t)와 b(i-t) 둘다 0으로 그냥 바꿔준다.

세번째로, 두번째에서 처리를 다 하고도 남은애들이 있다. 앞에서 처리 못해줬거나 1~t번째에서 뒤쪽 B에 1이 있는 경우

 for i=1~n : if b(i+t)==1 : a(i)=1, b(i-t) = b(i+t) = 0.

위와같이 처리해주면 정답 :)

 

소스코드

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

int A[100001], n, t;
char B[100001];

int main(){
	int T, N;
	scanf("%d",&T);
	for(int N=0; N<T; ){
		scanf("%d%d",&n,&t);
		scanf("\n%s",B);
		printf("Case #%d\n",++N);
		for(int i=0; i<n; ++i) A[i]=-1;
		for(int i=0; i<n; ++i) {
			if(B[i]=='0'){
				if(i-t>=0) A[i-t]=0;
				if(i+t<n) A[i+t]=0;
			}
		}
		for(int i=0; i<n; ++i){
			if(A[i]==-1) if(i-t>=0 && B[i-t]=='1') A[i]=1;
			if(A[i]==1){
				if(i-t>=0) B[i-t]='0';
				if(i+t<n) B[i+t]='0';
			}
		}
		for(int i=0; i<n; ++i){
			if(A[i]==-1){
				if(i+t<n && B[i+t]=='1') A[i]=1, B[i+t]='0';
				else if(i-t>=0 && B[i-t]=='1') A[i]=1, B[i-t]='0';
				else A[i]=0;
			}
		}
		for(int i=0; i<n; ++i) printf("%d",A[i]); puts("");
	}
}

 

3. No Cycle

문제 요약

사이클이 없는 방향 그래프가 주어진 후 k개의 무방향 간선이 주어진다.

k개의 무방향 간선 입력은 a, b로 주어지는데 사이클이 없도록 방향을 정해줬을 때 

a->b로 연결되면 0, b->a로 연결되면 1로 나타낸다.

입력받은 무방향간선들을 0 또는 1로 표시했을 때 사전순 가장 작은 경우를 구해라

1<=T<=70 (테스트 케이스)

3<=n<=500

0<=m<=2000

1<=k<=2000

 

풀이

이것도 그리디다.

일단 방향 간선을 모두 입력받은 후 그래프를 그려준다.

그다음 무방향 간선 쿼리에 대해 다음과 같이 생각한다.

1. if a->b로 가는 경로가 이미 존재한다 : 0출력

2. if b->a로 가는 경로가 이미 존재한다 : 1출력

3. else a->b 간선을 새로 이어주고 0출력

이게 TLE가 나기 쉽도록 되어있어서 처음에 n작다고 플루이드 와샬 썼다가 고생했는데,

그냥 bfs든 dfs든 잘 써서 a, b간의 관계를 구하면 되는 것 같더라

대충 O(k(n+m)) ?

 

소스코드

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

vector<ll> v[501], w[501];

bool bfs1(int a,int b){
	ll e[501]={0}, s=0;
	vector<ll> t;
	t.push_back(a); e[a]=1;
	for(ll f=t[s]; s<t.size(); ++s){
		
		for(int j=0; j<v[f].size(); ++j){
			if(!e[v[f][j]]){
				if(v[f][j]==b) return true;
				e[v[f][j]]=1;
				t.push_back(v[f][j]);
			}
		}
	}
	return false;
}

bool bfs2(int b, int a){
	ll e[501]={0}, s=0;
	vector<ll> t;
	t.push_back(a); e[a]=1;
	for(ll f=t[s]; s<t.size(); ++s){
		for(int j=0; j<w[f].size(); ++j){
			if(!e[w[f][j]]){
				if(w[f][j]==b) return true;
				e[w[f][j]]=1;
				t.push_back(w[f][j]);
			}
		}
	}
	return false;
}

int main(){
	ll T, N;
	scanf("%lld",&T);
	for(ll N=0; N<T; ){
		printf("Case #%lld\n",++N);
		ll n, m, q;
		scanf("%lld%lld%lld",&n,&m,&q);
		for(int i=1; i<=n; ++i) v[i].clear(), w[i].clear();
		for(int i=0; i<m; ++i){
			ll a, b;
			scanf("%lld%lld",&a,&b);
			v[a].push_back(b);
			w[b].push_back(a);
		}
		for(int i=0; i<q; ++i){
			ll a, b;
			scanf("%lld%lld",&a,&b);
			bool A=bfs1(a,b), B=bfs2(b,a);
			if(A) printf("0");
			else if(B) printf("1");
			else {
				printf("0");
				v[a].push_back(b);
				w[b].push_back(a);
			}
		}
		puts("");
	}
}

 

4. 예약 시스템

문제 요약

2행 m열의 호텔이 있다.

한 호실에 대해 맞은편 및 좌우 호실은 인접한 호실이라 한다.

단체 손님들이 들어오는데 각 단체는 4명 이상이다.

각 손님마다 값 w를 지니는데 인접한 호실의 손님이 다른 단체면 충돌을 일으킨다. 두 손님이 a, b일 때 그 값은 wa+wb

충돌을 최소화 할 수 있도록 손님들을 배치하자

1<=T<=67 (테스트 케이스)

2<=n<=20000

5<=m<=50000

 

풀이

이거도 그리디라 봐야할까? 싶다.

같은 그룹끼리 묶어두면 다른 그룹과 마주치는 손님은 4명으로 한정된다. 이 값을 가장 작은 값으로 둬야한다.

손님 수가 총 2m이므로 홀수그룹은 홀수개일 수 없다. 즉 짝지어 있다.

이 경우 홀수그룹 2개의 가장 작은 손님은 충돌을 한번씩 더 하게 된다.

 

경우의 수를 조금 나눠볼 수 있다.

i) 짝수 그룹만 있음

모든 4개 손님에 대해 더한 후 양 가 호실에 거주하는 그룹의 가장 큰 2값씩 총 4개를 빼준다.

즉, res = (1a+2a+3a+4a) + (1b+2b+3b+4b) - (3a+4a + 3b+4b) (단, 1a<=2a<=3a<=4a 및 b도 동일)

 

ii) 홀수 그룹만 있음

짝수그룹과 같은 계산을 한 후 튀어나와있는 1a, 1b에 대해서 서로 충돌하므로 한번씩 더 더해준다.

즉, res = (1a+2a+3a+4a + 1a) + (1b+2b+3b+4b + 1b) - (3a+4a + 3b+4b)

 

iii) 섞여있는 그룹

경우의 수를 조금 나눌 수 있다.

 a) 홀수그룹이 2개일 때 특수케이스 / 홀짝홀

이 경우 짝수들의 가장 작은 2값들을 모두 한번씩 더 더해줘야한다.

해당하는 예제는 홀수 그룹 2개의 큰값 2개들이 매우 큰 경우이다.

 

 b) 홀 짝 (or 짝 홀)

 c) 짝 홀 짝

 d) 홀짝홀(홀수그룹 >=4)

a), b), c), ( d) ) 중 가장 큰 값을 출력해주면 된다.

 

소스코드

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll MX(ll a, ll b){return a>b?a:b;}
vector<ll> v1, v2; // v1 odd, v2 even

int main(){
	ll T, N;
	scanf("%lld",&T);
	for(ll N=0; N<T; ){
		v1.clear(), v2.clear();
		printf("Case #%lld\n",++N);
		ll n, m, res=0, even=0;
		scanf("%lld%lld",&n,&m);
		for(ll i=0; i<n; ++i){
			ll l, a[5];
			scanf("%lld",&l);
			for(ll j=0; j<4; ++j) scanf("%lld",&a[j]);
			for(ll j=4; j<l; ++j) scanf("%lld",&a[4]), sort(a,a+5);
			res+=a[0]+a[1]+a[2]+a[3];
			if(l&0x01) v1.push_back(a[2]+a[3]), res+=a[0];
			else v2.push_back(a[2]+a[3]), even+=a[0]+a[1];
		}
		sort(v1.begin(),v1.end());
		sort(v2.begin(),v2.end());
		if(n==1) res=0;
		else if(!v1.size()) res-=(v2[v2.size()-1]+v2[v2.size()-2]);
		else if(!v2.size()) res-=(v1[v1.size()-1]+v1[v1.size()-2]);
		else { // v1>=2 & v2>=1
			ll mx = MX( v1[v1.size()-1]+v1[v1.size()-2]-even, v1[v1.size()-1]+v2[v2.size()-1] ); // ?€ 吏??€ / ?€ 吏?
			if(v1.size()>3) mx=MX(mx,v1[v1.size()-1]+v1[v1.size()-2]);
			if(v2.size()>1) mx=MX(mx,v2[v2.size()-1]+v2[v2.size()-2]); // 吏??€ 吏?
			res-=mx;
		}
		printf("%lld\n",res);
	}
}

 

5. 차이

문제 요약

n개의 변수가 있다. Xi로 나타낼 때, k개의 명령이 주어진다.

각 명령에 대해 첫 입력은 종류를 나타낸다.

1인 경우 a, b, c를 입력받고 X(a) - X(b) = c임을 나타낸다.

2인 경우 a, b를 입력받고 X(a) - X(b)가 얼마인지 유추한다.

유추 가능한 경우가 여러가지인 경우 "CF"

유추 가능한 경우가 하나인 경우 그 값 출력

유추 불가능한 경우 "NC" 출력

1<=n<=100000

1<=k<=200000

 

풀이

그래프라 생각했을 때 하나의 그래프 집합 내에서 임의의 두 노드사이에 가중치가 다른 경로가 여러가지가 있다면 그 집합의 노드간 연결은 모두 CF이다.

또한 서로 다른 집합의 노드 간 연결은 NC이다.

그 이외의 경우 출력해주면 된다.

 

unionfind를 사용한다.

R : 각 노드에서 자신의 부모를 저장해두고

D : 각 노드에서 루트까지 거리(X(root) - X(i)의 값)를 저장하고

V : 해당 집합 내에 CF가 될만한게 있는지 체크한다

1번 쿼리가 들어왔을 때,

i) a, b의 루트가 다른 경우

경로를 이어준다.

ii) a, b의 루트가 같은 경우

기존에 있던 D(a)-D(b)값이 c와 다르다면 V(root) 체크 및 업데이트(는 나중에 알아서 합니다)

2번 쿼리가 들어왔을 때,

i) a, b의 루트가 같은 경우

루트의 V가 체크되어있다면 "CF" 그렇지 않다면 D(a)-D(b)를 출력

ii) a, b의 루트가 다른 경우

"NC" 출력

 

소스코드

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

ll r[110000], d[110000], v[110000];

ll uf(ll x){
	if(x==r[x]) return x;
	ll t=uf(r[x]);
	d[x]+=d[r[x]];
	v[r[x]]=v[x]=(v[r[x]]|v[x]);
	return r[x]=t;
}

int main(){
	ll T, N;
	scanf("%lld",&T);
	for(ll N=0; N<T; ){
		printf("Case #%lld\n",++N);
		ll n, k;
		scanf("%lld%lld",&n,&k);
		for(ll i=0; i<=n; ++i) r[i]=i, d[i]=v[i]=0;
		while(k--){
			ll o, a, b;
			scanf("%lld%lld%lld",&o,&a,&b);
			ll x=uf(a), y=uf(b);
			if(o==1){
				ll c;
				scanf("%lld",&c);
				//if(x>y){ll t=x; x=y; y=t;c=-c;}
				if(x!=y){
					r[x]=y;
					v[x]=v[y]=(v[x]|v[y]);
					d[x]=d[b]-d[a]+c;
				}
				else if(d[a]-d[b]!=c) v[x]=1; // CF
			}
			else{
				if(x!=y) printf("NC\n");
				else {
					if(v[x]) printf("CF\n");
					else printf("%lld\n",d[a]-d[b]);
				}
			}
		}
	}
}

 

반응형

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

UCPC 2022 예선 후기  (2) 2022.07.11
진짜 최종 구데기컵 2 2 검수 후기  (2) 2022.04.10
ICPC 2021 후기  (0) 2021.11.15
Facebook Hacker Cup 2021 후기  (0) 2021.09.26
SCPC 2021 2차 대회 후기  (0) 2021.08.10