문제

https://codeforces.com/contest/1150/problem/E

 

Problem - E - Codeforces

 

codeforces.com

 

풀이

우선 괄호열과 트리 간의 어떤 관계가 있는지를 관찰해보자.

s=(()(()))이라고 하면, 트리는 다음과 같이 그려진다.

트리를 주의깊게 살펴보면 depth가 늘어날때 여는 괄호이고, depth가 줄어들때 닫는 괄호이다.

즉, 여는 괄호가 나오면 새로 자식노드를 만들고, 닫는괄호가 나오면 퇴각하면 되는 것이다.

따라서, 지금까지의 괄호열의 prefix에서 (여는 괄호의 개수)-(닫는 괄호의 개수)가 tree에서의 depth가 되는 것이다.

 

여기서 구하고자 하는 tree의 지름은 두 노드 사이의 거리의 최대값이 된다.

두 노드 사이의 거리는 다음과 같이 표현할 수 있다.

$$d(u,v)=depth(u)+depth(v)-2depth(lca(u,v))$$

 

여기서 $lca(u,v)$는 두 노드의 lowest common ancestor이다.

괄호열이 트리를 순서대로 순회하고 있으므로, $lca(u,v)$는 u와 v사이의 depth값중 가장 작은 값을 가지는 점이 된다.

따라서 문제에서 구해야 할 것은 다음과 같다.

$$\max(depth(u)+depth(v)-2depth(k))(u \leq k \leq v)$$

 

이것을 단순히 계산하면 depth를 미리 계산해 놓는다 하여도,  쿼리마다 $O(n^3)$의 시간복잡도가 필요하다.

이를 쿼리마다 빠르게 계산하기 위해서, segment tree를 사용한다.

 

segment tree의 각 노드에서 관리하는 값을 하위 구간의 $diam=\max(depth(u)+depth(v)-2depth(k))(u \leq k \leq v)$라 하자.

그러면 다음과 같은 경우로 나눌 수 있다.

 

case 1) u,k,v가 전부 왼쪽 자식에 있을 경우

이 경우는 왼쪽 자식의 $diam$이 정답이 된다.

 

case 2) u,k는 왼쪽자식, v는 오른쪽 자식에 있을 경우

이 경우에는 왼쪽에서의 $\max(depth(u)-2depth(k))$ 와 오른쪽의 $\max(depth(v))$의 합이 답이 된다.

단, 여기서 주의해야 할것은, 현재 오른쪽 자식에 있는 $depth(v)$는 현재 왼쪽자식의 정보없이 구한 값이다.

따라서, $\max(depth(v))$에 현재의 depth를 더해주어야 한다.

 

case 3) u는 왼쪽자식, k,v는 오른쪽 자식에 있을 경우

이 경우는 왼쪽의 $\max(depth(u))$와 오른쪽의 $\max(depth(v)-2depth(k))$의 합이 답이 된다.

case 2와 비슷하게, 오른쪽 자식의 $depth(v)$는 현재 왼쪽자식의 정보없이 구한 값이다.

따라서 각각의 depth에 현재의 depth를 더해주어야 하므로, $\max(depth(v)-2depth(k))$에 현재 depth를 빼준다.

 

case 4) u,k,v 모두 오른쪽 자식에 있을 경우

이 경우 오른쪽 자식의 $diam$이 정답이 된다.

depth의 변동이 있지만 모두 더하면 0이므로 그냥 값을 그대로 사용한다.

 

이 4가지 case중 최대값이 구하고자 하는 $diam$의 값이 된다. 

여기서 추가적으로 $\max(depth(v)-2depth(k))$와 $\max(depth(u)-2depth(k))$도 구해야 함을 알 수 있다.

 

위의 방법과 똑같은 방법으로 각각을 구할 수 있다.

각각 3가지의 case가 나오고, 코드가 아래에 있으니 과정은 생략하겠다.

-가 붙은 부분의 최대값을 구할 때, 최소값으로 바꾸어야 함을 주의하자.

 

따라서 이 과정으로 segment tree를 이용하여 $diam$을 구하기 위해서 구간 $I$에 해당하는 노드에서

현재 depth

$\max(depth(v)) (v \in I)$

$\min(depth(v)) (v \in I)$

$\max(depth(v)-2depth(k)) (k \leq v \in I)$

$\max(depth(u)-2depth(k)) (u \leq k \in I)$

$\max(depth(u)-2depth(k)+depth(v)) (u \leq k \leq v \in I)>$

를 관리하면 되며, 쿼리가 들어올 때마다 leaf노드를 바꿔주고 부모노드로 올라가면서 update를 해주면 된다.

이 과정은 $O(log n)$만에 가능하다.

 

따라서 총 시간복잡도는 트리의 초기화 $O(n)$ 과 쿼리당 $O(log n)$으로 아래와 같다.

 

Time Complexity : $O(n+qlogn)$

 

코드

#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#define lc(i) ((i)<<1)
#define rc(i) (((i)<<1)+1)
#define pa(i) ((i)>>1)
#define N 1<<19

using namespace std;

struct segnode
{
	int sum;
	int psummax; 
	int psummin;
	int leftmax;
	int rightmax;
	int diam;
} seg[N];

int n,q, p,l;
string s;
segnode merge(segnode x, segnode y)
{
	segnode ret;
	ret.sum = x.sum+y.sum;
	ret.psummax = max(x.psummax, x.sum+y.psummax);
	ret.psummin = min(x.psummin, x.sum+y.psummin);
	ret.leftmax = max( max(x.leftmax, y.leftmax-x.sum), x.psummax - (x.sum+y.psummin)*2);
	ret.rightmax = max( max(x.rightmax, y.rightmax-x.sum), x.sum+y.psummax - x.psummin*2);
	ret.diam = max( max(x.diam, y.diam), max(x.leftmax+x.sum+y.psummax, x.psummax+y.rightmax-x.sum));
	return ret;
}
segnode leaf(int x)
{
	segnode ret;
	ret.sum=x;
	ret.psummax= (x==1?1:0);
	ret.psummin= (x==1?0:-1);
	ret.leftmax= 1-x;
	ret.rightmax= 1;
	ret.diam =1;
	return ret;
}
void update(int x)
{
	seg[x+p] = (s[x]=='(')?leaf(1):leaf(-1);
	x+=p;
	while(x!=1)
	{
		x>>=1;
		seg[x] = merge(seg[lc(x)], seg[rc(x)]);
	}
}

void init()
{
	for(int i=0;i<l;i++) seg[i+p]= (s[i]=='(')?leaf(1):leaf(-1);
	for(int i=p-1;i>0;i--) seg[i]=merge( seg[lc(i)], seg[rc(i)]);
}
void printnode(int i)
{
	cout<<seg[i].sum<<" "<<seg[i].psummax<<" "<<seg[i].psummin<<" ";
	cout<<seg[i].leftmax<<" "<<seg[i].rightmax<<" "<<seg[i].diam<<" ";
	cout<<"\n";

}
int main()
{
	cin>>n>>q>>s;
	l=2*n-2;
	for(p=l;p^(-p&p);p+=(-p&p));
	
	init();
	cout<<seg[1].diam<<"\n";
	while(q--)
	{
		int x,y;
		cin>>x>>y;
		--x,--y;
		swap(s[x],s[y]);
		update(x);
		update(y);
		cout<<seg[1].diam<<"\n";
	}
}

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

[codeforces] 1150D. Three Religions  (0) 2019.04.30
[codeforces] 1150C. Prefix Sum Primes  (0) 2019.04.30
[codeforces] 1150B. Tiling Challenge  (0) 2019.04.30
[codeforces] 1150A. Stock Arbitraging  (0) 2019.04.30

문제

https://codeforces.com/contest/1150/problem/D

 

Problem - D - Codeforces

 

codeforces.com

 

풀이

3차원 dp를 이용하여 해결한다.

쿼리 형의 문제가 아니라 일반적인 DP라고 생각하고, 모든 문자가 미리 다 들어와있다고 가정하고 문제를 먼저 풀자.

 

$dp(i,j,k)$ = 1번, 2번 3번 religion description이 각각 i글자, j글자, k글자일 때, 필요한 Word of Universe의 최소길이

 

라고 정의하자. 

그러면 $dp(i,j,k)$$dp(i-1,j,k)$에서 한글자가 추가된 경우와, $dp(i,j-1,k)$에서 한글자가 추가된 경우, $dp(i,j,k-1)$에서 한글자가 추가된 경우. 이 세가지의 경우 중에서 가장 작은 경우의 답이 된다.

각 케이스는 서로 대칭이므로 첫 번째 케이스만 보자.

 

현재 $dp(i-1,j,k)$만큼의 Word of Universe를 사용하고 있다.

이 때 새로운 글자 c가 추가될 때 $dp(i,j,k)$는 Word of Universe 위에서 $dp(i-1,j,k)$ 이후에 나타나는 가장 가까운 c의 위치가 된다.

 

따라서 이를 빠르게 계산하기 위해서 $nx(i,j)$라는 배열을 만드는데 $nx(i,j)$는 Word of Universe 상에서 i+1~끝까지 배열에서 가장 가까운 알파벳 j의 위치이며, 이를 미리 만드는데에는 $O(n * |A|)$이면 충분하다. ($|A|$는 사용하는 알파벳의 개수) 

 

따라서, 이 nx를 이용해서 dp 관계식을 엄밀히 세워보면 다음과 같다.

$$dp(i,j,k)=\min(nx(dp(i-1,j,k), des_1(i-1)), nx(dp(i,j-1,k), des_2(j-1)), nx(dp(i,j,k-1), des_3(k-1)))$$

단, $des_i$는 i 종교의 description 목록이다.

 

이 점화식을 바탕으로 dp배열을 구현한다.

+ query가 들어올 경우 해당하는 글자를 하나 늘리고, 해당 글자가 늘어나서 값이 추가되는 부분만 새로 구해주면 된다. 이 부분의 개수는 쿼리 한 번에 $O(n^2)$이다.

 

- query가 들어올 경우 단순히 description 개수만 한개 빼주면 된다. 이미 채워져있는 테이블은 무시한다.

 

 

Time Complexity : $O(n|A|+qn^2)$

 

코드

#include <iostream>
#include <vector>
#include <string>

#define INF 1<<30
#define N 251
#define L 100010
#define A 26
using namespace std;

string wou;
int n,q;
int nx[L][A], p[A];
int dp[N][N][N], len[4], pre[4];
char des[4][L];

int main()
{
	cin>>n>>q>>wou;
	wou=" "+wou;
	for(int i=0;i<26;i++)
	{
		nx[n+1][i]=n+1;
		p[i]=n+1;
	}
	for(int i=n;i>=0;i--)
	{
		for(int j=0;j<26;j++) nx[i][j]=p[j];
		if(i) p[wou[i]-'a'] = i;
	}

	while(q--)
	{
		string type,c;
		int x;
		cin>>type>>x;

		for(int i=1;i<4;i++) pre[i]=0;
		if(type[0]=='-')
		{
			len[x]--;
			cout<< (dp[len[1]][len[2]][len[3]]<=n?"YES":"NO")<<"\n";
			continue;
		}
		cin>>c;
		pre[x] = len[x];
		des[x][len[x]++] = c[0];

		for(int i=pre[1];i<=len[1];i++) for(int j=pre[2];j<=len[2];j++) for(int k=pre[3];k<=len[3];k++)
		{
			if(i==0 && j==0 && k==0)continue;
			dp[i][j][k] = INF;
			if(i) dp[i][j][k]= min(dp[i][j][k], nx[ dp[i-1][j][k] ][ des[1][i-1]-'a' ]);
			if(j) dp[i][j][k]= min(dp[i][j][k], nx[ dp[i][j-1][k] ][ des[2][j-1]-'a' ]);
			if(k) dp[i][j][k]= min(dp[i][j][k], nx[ dp[i][j][k-1] ][ des[3][k-1]-'a' ]);	
		}
		cout<< (dp[len[1]][len[2]][len[3]]<=n?"YES":"NO")<<"\n";
	}
}

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

[codeforces] 1150E. Tree Generator™  (0) 2019.05.01
[codeforces] 1150C. Prefix Sum Primes  (0) 2019.04.30
[codeforces] 1150B. Tiling Challenge  (0) 2019.04.30
[codeforces] 1150A. Stock Arbitraging  (0) 2019.04.30

문제

https://codeforces.com/contest/1150/problem/C

 

Problem - C - Codeforces

 

codeforces.com

 

풀이

2를 제외한 모든 소수는 홀수이다.

따라서, 주어진 순열을 2 1 2 2 2 ... 2 2 1 ... 1 1 1의 형태로 놓는다면, $\sum a_i$이하의 모든 소수를 포함하게 된다.

이것이 불가능한 경우는, 모두 1이거나 모두 2인 경우뿐이므로, 이 경우만 따로 출력한다.

 

Time Complexity : $O(n)$

 

코드

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

int main()
{
	int n, onenum=0, twonum=0;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		int x;
		scanf("%d",&x);
		if(x==1) onenum++;
		else twonum++;
	}

	if(onenum==0)
	{
		for(int i=0;i<n;i++) cout<<"2 ";
	}
	else if(twonum==0)
	{
		for(int i=0;i<n;i++) cout<<"1 ";
	}
	else
	{
		cout<<"2 1 ";
		for(int i=0;i<twonum-1;i++) cout<<"2 ";
		for(int i=0;i<onenum-1;i++) cout<<"1 ";
	}
}

 

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

[codeforces] 1150E. Tree Generator™  (0) 2019.05.01
[codeforces] 1150D. Three Religions  (0) 2019.04.30
[codeforces] 1150B. Tiling Challenge  (0) 2019.04.30
[codeforces] 1150A. Stock Arbitraging  (0) 2019.04.30

문제

https://codeforces.com/contest/1150/problem/B

 

Problem - B - Codeforces

 

codeforces.com

 

풀이

왼쪽 위부터 순서대로 채운다. 이렇게 채워도 가능한 모든 경우를 다 볼수 있다는 것이 증명 가능하다.

 

# # # # #
#      
   
       

저 첫 빈칸을 채우기 위해서는 저 첫 빈칸을 위쪽타일로 하는 십자타일을 놓는 경우 외에는 존재하지 않는다.

따라서, 이 타일을 전부 채울수 있는 경우가 있다면, 저 곳은 o로 포함된 타일 모양으로 채워야 하고, 이를 반복적으로 진행하여, 다 채울 수 있는지 확인한다.

 

Time Complexity : $O(n^2)$

 

코드

#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#define N 52
using namespace std;

int n;
string mp[N];
bool f(int p,int q)
{
	if(p<0 || q<0 || p>=n || q>=n) return false;
	return mp[p][q]=='.';
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++) cin>>mp[i];

	for(int i=0;i<n*n;i++)
	{
		if(mp[i/n][i%n]=='#')continue;
		int x=i/n, y=i%n;
		if( !f(x,y) || !f(x+1, y-1) || !f(x+1, y) || !f(x+1, y+1) || !f(x+2, y))
		{
			cout<<"NO";
			return 0;
		}
		mp[x][y]=mp[x+1][y-1]=mp[x+1][y]=mp[x+1][y+1]=mp[x+2][y]='#';
	}
	cout<<"YES";
}

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

[codeforces] 1150E. Tree Generator™  (0) 2019.05.01
[codeforces] 1150D. Three Religions  (0) 2019.04.30
[codeforces] 1150C. Prefix Sum Primes  (0) 2019.04.30
[codeforces] 1150A. Stock Arbitraging  (0) 2019.04.30

문제

https://codeforces.com/contest/1150/problem/A

 

Problem - A - Codeforces

 

codeforces.com

 

풀이

가장 저렴하게 구매해서, 가장 비싸게 파는 것이 유리하다.

$smin=\min(s_i)$와 $bmax=\max(b_i)$를 구하자.

가능한 만큼, $smin$원에 구매하고, $smax$원에 판매하면 된다.

 

Time Complexity : $O(n+m)$

 

코드

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;
int main()
{
	int n,m,r,x;
	int smin=10000,bmax=0;

	cin>>n>>m>>r;
	for(int i=0;i<n;i++)
	{
		cin>>x;
		if(smin>x)smin=x;
	}
	for(int i=0;i<m;i++)
	{
		cin>>x;
		if(bmax<x)bmax=x;
	}

	if(smin>=bmax) cout<<r;
	else cout<<(r/smin)*bmax+r%smin;
}

 

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

[codeforces] 1150E. Tree Generator™  (0) 2019.05.01
[codeforces] 1150D. Three Religions  (0) 2019.04.30
[codeforces] 1150C. Prefix Sum Primes  (0) 2019.04.30
[codeforces] 1150B. Tiling Challenge  (0) 2019.04.30

+ Recent posts