https://atcoder.jp/contests/abc214
rmq 다이나믹 세그먼트 트리를 잘못구현해서 한시간반동안 디버깅했다.
A. New Generation ABC
Tag : casework
풀이
하라는대로 하면 된다.
전체코드
#include <bits/stdc++.h>
#define endl '\n'
#define fi first
#define se second
#define all(x) ((x).begin()),((x).end())
#define compress(x) sort(all(x)), (x).erase(unique(all(x)),(x).end())
using namespace std;
using ll = long long;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int n; cin >> n;
if(1 <= n and n <= 125) cout << 4;
if(126 <= n and n <= 211) cout << 6;
if(212 <=n and n<=214) cout << 8;
}
B. How many?
Tag : BruteForce
풀이
가능한 모든 \(i,j,k\) 쌍에 대해서 \(i+j+k<=S\)와 \(ijk<=T\) 여부를 확인해주면 된다.
전체코드
#include <bits/stdc++.h>
#define endl '\n'
#define fi first
#define se second
#define all(x) ((x).begin()),((x).end())
#define compress(x) sort(all(x)), (x).erase(unique(all(x)),(x).end())
using namespace std;
using ll = long long;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
ll ans = 0;
ll S, T; cin >> S >> T;
for(ll i = 0; i<=S;i++){
for(ll j = 0;j<=S;j++){
for(ll k=0; k<=S; k++) {
if(i+j+k<=S and i*j*k<=T) ans++;
}
}
}
cout << ans;
}
C. Distribution
Tag : Math?
풀이
\(i\)가 보석을 받는 경우는 딱 두가지이다. \(Takahashi\)에게 바로 보석을 받거나, \(i-1\)에게 보석을 받는 경우이다.
그대로 구현하면 된다.
전체코드
#include <bits/stdc++.h>
#define endl '\n'
#define fi first
#define se second
#define all(x) ((x).begin()),((x).end())
#define compress(x) sort(all(x)), (x).erase(unique(all(x)),(x).end())
using namespace std;
using ll = long long;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int N; cin >> N;
vector<int> s(N); for(auto &i : s) cin >> i;
vector<int> t(N); for(auto &i : t) cin >> i;
vector<int> ans(N); for(int i = 0; i<N; i++) ans[i] = t[i];
int idx = min_element(all(t))-t.begin();
for(int i = idx; i<=idx+N; i++){
ans[i%N] = min(t[i%N], ans[(i-1+N)%N]+s[(i-1+N)%N]);
}
for(int i = 0; i<N; i++) cout << ans[i] << endl;
}
D. Sum of Maximum Weights
Tag : DSU, Greedy
문제 요약
\(f(u,v)=u\)에서 \(v\)로 가는 최단 경로 중 가장 긴 간선의 길이
\(\sum_{i=1}^{N-1}\sum_{j=i+1}^{N}f(i,j)\)를 \(O(NlogN)\)쯤에 구해라.
풀이
임의의 간선 \(E\)를 \(f(i,j)\)의 값으로 가지는 \((i,j)\) 쌍을 구하려면 \(E\)보다 큰 간선을 모두 지운 트리에서
\(E\)의 어느 한쪽과 연결된 컴포넌트의 크기와 반대쪽 컴포넌트의 크기를 곱하면 구할 수 있다. 이것을 이용해서 모든 간선을 길이 기준으로 정렬해 오름차순으로 연결해주면 각 \(E\)에서는 트리에 \(E\)보다 큰 간선이 존재하지 않는다.
양쪽 컴포넌트의 크기는 유니온 파인드로 관리해주면 된다.
전체코드
#include <bits/stdc++.h>
#define endl '\n'
#define fi first
#define se second
#define all(x) ((x).begin()),((x).end())
#define compress(x) sort(all(x)), (x).erase(unique(all(x)),(x).end())
using namespace std;
using ll = long long;
int N; ll ans = 0;
ll pa[101010], sz[101010];
int find(int a){
if(a == pa[a]) return a;
else return pa[a] = find(pa[a]);
}
void merge(int a, int b){
a=find(a),b=find(b);
if(a==b)return;
sz[a]+=sz[b];
pa[b]=a;
}
struct pos{
ll x,y,z;
bool operator<(pos a){ return z < a.z; }
};
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> N;
for(int i = 1; i<=N; i++) pa[i]=i,sz[i]=1;
vector<pos> edge(N-1);
for(auto &[i,j,w] : edge) cin >> i >> j >> w;
sort(all(edge));
for(auto &[i,j,w] : edge){
ans += w * sz[find(i)] * sz[find(j)];
merge(i,j);
}
cout << ans << endl;
}
E. Packing Under Range Regulations
Tag : Greedy, DSU, Dynamic Segment Tree
문제 요약
박스가 \(1\)번부터 \(10^9\)까지 존재하고 \(i\)번 공은 \([L_i,R_i]\)에 들어가야 한다. 모든 공을 박스에 담지 못하는 경우가 있는지 판별하라.
풀이1
모든 구간을 오른쪽 끝을 기준으로 정렬하고 구간마다 아직 사용하지 않은 박스 중 가장 왼쪽에 담으면 된다. 사용하지 않은 박스중 최소를 어떻게 찾냐가 문제가 되는데 내가 대회 중 생각한 풀이는 다음과 같다. Dynamic Segment Tree를 만들고 초기에 점\(i\)에는 \(i\)값을 가지도록 한다. 그리고 각 구간마다 사용할 박스를 고른 후 사용한 박스를 굉장히 큰 값으로 업데이트한다. 그렇게되면 구간의 \(min\)값을 구할 때마다 사용하지 않은 가장 왼쪽 박스를 얻을 수 있다. 테스트케이스마다 초기화를 해주면 TLE의 위험이 있기때문에 Dynamic Segment Tree를 포인터로 구현했는데 이 때문인지 구현 실수를 굉장히 많이 저지르게 됐고 60분쯤 제출을 했지만 WA를 받았다. 이후 대회가 끝날 때까지 AC를 받지 못했다.
대회가 끝난 후 비요뜨가 디버깅을 도와줘서 겨우 업솔빙했다.
풀이1 전체 코드
#include <bits/stdc++.h>
#define endl '\n'
#define pb push_back
#define fi first
#define se second
#define all(x) ((x).begin()),((x).end())
#define compress(x) sort(all(x)), (x).erase(unique(all(x)),(x).end())
using namespace std;
using ll = long long;
struct Node{
Node *l, *r;
ll v;
Node(){ l=r=0,v=2e18; }
};
void update(Node *node, ll s, ll e, ll x, ll v){
if(s == e){ node->v = 2e18; return; }
ll m = (s+e)/2;
if(x <= m){
if(!node->l) node->l = new Node();
update(node->l, s, m, x, v);
}else{
if(!node->r) node->r = new Node();
update(node->r, m+1, e, x, v);
}
ll L = node->l ? node->l->v : s;
ll R = node->r ? node->r->v : m+1;
node->v = min(L,R);
}
ll query(Node *node, ll s, ll e, ll l, ll r){
if(r < s or e < l) return 2e18;
if(node==0) return s;
if(l <= s and e <= r) return node->v;
ll m = (s+e)/2;
return min(query(node->l, s, m, l, r),query(node->r, m+1, e, l, r));
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int tc; cin >> tc;
while(tc--){
int N; cin >> N;
vector<pair<ll,ll>> v(N); for(auto &[i,j]: v) cin >> i >> j;
sort(all(v),[&](pair<ll,ll>a,pair<ll,ll>b){ return a.se < b.se; });
Node *root = new Node();
int f = 1;
for(auto &[i, j] : v){
ll q = query(root,1,2e9,i,j);
if(q > j or q > 1e9) {
f = 0; break;
}
update(root,1,2e9,max(q,i),2e18);
}
if(f) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
풀이2
path compression을 유용하게 사용하는 풀이이다. \(find(i)=[i,\infty)\)의 범위에서 사용되지 않은 박스 중 가장 왼쪽의 번호로 정의하면 \(DSU\)와 같은 구현을 써먹을 수 있다. 물론 시간복잡도 또한 다이나믹 세그먼트 트리보다 작다.
풀이2 전체코드
#include <bits/stdc++.h>
#define endl '\n'
#define pb push_back
#define fi first
#define se second
#define all(x) ((x).begin()),((x).end())
#define compress(x) sort(all(x)), (x).erase(unique(all(x)),(x).end())
using namespace std;
using ll = long long;
unordered_map<ll,ll>pa;
ll find(ll a){
if(pa[a]==0)return a;
else return pa[a]=find(pa[a]);
}
void merge(ll a, ll b){
pa[find(b)]=pa[find(a)];
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int tc; cin >> tc;
while(tc--){
pa.clear();
int N,f=1; cin >> N;
vector<pair<ll,ll>> v(N); for(auto &[i,j]: v) cin >> i >> j;
sort(all(v), [&](pair<ll,ll>&a,pair<ll,ll>&b){ return a.se<b.se; });
for(auto &[i,j]:v){
ll q=find(i);
if(q>j){ f=0; break; }
pa[q]=q+1;
}
if(f)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}