Codeforces Round 874 (Div. 3)


A. Musical Puzzle

A. Musical Puzzle

인접한 $s_i s_{i+1}$ 이 유일한 것의 개수를 세자.

B. Restore the Weather

B. Restore the Weather

정렬해서 올바르게 묶어주면 된다.

void solve() {
   int n, k;
   cin >> n >> k;
   vi a(n), b(n);
   fv(a);
   fv(b);
   vi idx(n);
   iota(all(idx), 0);
   sort(all(idx), [&](int i, int j) { return a[i] < a[j]; });
   sort(all(b));
   vi ans(n);
   for (int i = 0; i < n; i++) {
      ans[idx[i]] = b[i];
   }
   for (int i: ans) cout << i << ' ';
   cout << endl;
}

C. Vlad Building Beautiful Array

C. Vlad Building Beautiful Array

중복된 값을 없애고 정렬하고 순회하며 지금까지 나온 것 중 짝수나 홀수가 있었는지를 파악한다.

자신이 짝수이고 현재까지 홀수가 안나왔다면 모두 홀수가 될 수 없다.

마찬가지로 자신이 홀수이고 현재까지 홀수가 안나왔다면 모두 짝수가 될 수 없다.

마지막에 모두 홀수가 되거나 모두 짝수가 될 수 있는지만 검사해주면 된다.

void solve() {
   int n;
   cin >> n;
   vi a(n);
   fv(a);
   uniq(a);
 
   n = sz(a);
   if (n == 1) {
      cout << "YES\n";
      return;
   }
 
   int can_even = 1, can_odd = 1;
   int even = 0, odd = 0;
   for (int i = 0; i < n; i++) {
      if (a[i] % 2 == 0 && !odd) {
         can_odd = 0;
      }
      if (a[i] % 2 == 1 && !odd) {
         can_even = 0;
      }
 
      if (a[i] % 2 == 0) even = 1;
      else odd = 1;
   }
   if(can_even || can_odd) cout << "YES\n";
   else cout << "NO\n";
}
 

D. Flipper

D. Flipper

몇가지 케이스를 나눠 모두 검사해보면 된다. $O(n^2)$ 으로 풀 수 있기 때문이다.

우선, 가장 큰 수가 가장 앞에 와야한다고 해보자.

가장 큰 수가 처음에 가장 앞에 와있으면 그렇게 만들 수 없다.

그럼 두 번째로 가장 큰 수가 가장 앞에 올 수 있는 대략 $O(n)$ 가지를 모두 검사해보는 것이다.

void solve() {
   int n;
   cin >> n;
   vi a(n);
   fv(a);
   for (int &i: a) i--;
   auto copy = a;
   int ni = maxi(a);
   vvi cand;
   reverse(all(a));
   cand.pb(a);
   a = copy;
 
   if (n == 1) {
      for (int i: a) cout << i + 1 << ' ';
      cout << endl;
      return;
   }
 
   if (ni == 0) {
      // 가장 앞에 있다면
      for (int i = 0; i < n; i++) if (a[i] == n - 2) ni = i;
      for (int l = (ni == n - 1 ? ni : ni - 1); l >= 0; l--) {
         a = copy;
         reverse(a.begin() + l, a.begin() + ni);
         vi b;
         for (int i = ni; i < n; i++) b.pb(a[i]);
         for (int i = l; i < ni; i++) b.pb(a[i]);
         for (int i = 0; i < l; i++) b.pb(a[i]);
         cand.pb(b);
      }
   } else {
 
      for (int l = 0; l < (ni == n - 1 ? ni + 1 : ni); l++) {
         a = copy;
         reverse(a.begin() + l, a.begin() + ni);
         vi b;
         for (int i = ni; i < n; i++) b.pb(a[i]);
         for (int i = l; i < ni; i++) b.pb(a[i]);
         for (int i = 0; i < l; i++) b.pb(a[i]);
         cand.pb(b);
         debug(b);
      }
   }
   sort(all(cand), greater<>());
   for (int i: cand[0]) cout << i + 1 << ' ';
   cout << endl;
}

E. Round Dance

E. Round Dance

그래프같긴 했는데 오늘은 Virtual로 풀어서 이건 못풀었다.


다시 생각해보았다.

근데 생각해보니 한 명만 기억하므로 아무런 사람 두 명 엮어서 그 두사람끼리 라운드를 진행했다고 하면 무한히 max를 늘릴 수 있는데 이 경우는 뭘까

씨팔출제자 description 인성어디

F. Ira and Flamenco

F. Ira and Flamenco

우선 수들을 정렬하고 이분탐색으로 현재 수가 $a_i$ 라고 한다면 $[a_i, a_i+m)$ 범위에 서로 다른 수가 몇개인지를 찾는다.

만약 그것이 $m$ 개 이상이라면 가능한 경우이다.

그런데 여기서 생각해보면 그것이 $m$ 개 초과가 될 수는 절대 없다는 것을 알 수 있다.

더 엄밀히 말하자면 저 범위에 수가 서로 다르려면 $a_{i+1}=a_i+1,~a_{i+2}=a_i+2, \dots$ 여야 한다는 것을 알 수 있다.

따라서 $[a_i,a_i+m)$ 범위에 모두 수가 있다면, 전처리해둔 각 수마다 나오는 횟수를 모두 곱해주고 정답에 더해주기만 하면 된다.

모두 곱해주는 방법은 구간곱 배열을 modular inverse를 이용해서 적절히 계산하여 $O(n \log n)$에 풀 수 있다.

const ll mod = 1e9 + 7;  
inline ll md(ll x) { return md(mod, x); }  
  
array<ll, 3> gcdx(ll A, ll B) {  
   ll x1 = 1, x2 = 0, y1 = 0, y2 = 1, r1 = A, r2 = B;  
   while (r2) {  
      ll q = r1 / r2;  
      if (r1 - r2 * q < 0) q--;  
      tie(x1, x2) = mp(x2, x1 - x2 * q);  
      tie(y1, y2) = mp(y2, y1 - y2 * q);  
      tie(r1, r2) = mp(r2, r1 - r2 * q);  
   }  
   return {x1, y1, r1};  
}  
int inverse(int x) {  
   return md(gcdx(x, mod)[0]);  
}  
  
template<class T>  
struct fenwick {  
   int n;  
   vector<T> tree;  
   fenwick(int n) : n(n) { tree.resize(n + 1); }  
   T sum(int i) {  
      T ret = 0;  
      for (; i; i -= i & -i) ret += tree[i];  
      return ret;  
   }  
   void update(int i, T x) { for (i++; i <= n; i += i & -i) tree[i] += x; }  
   T query(int l, int r) { return l > r ? 0 : sum(r + 1) - sum(l); }  
};  
  
  
void solve() {  
   int n, m;  
   cin >> n >> m;  
   vi a(n), can(n);  
   fv(a);  
   sort(all(a));  
  
   fenwick<int> fenw(n);  
  
   map<int, int> cnt;  
   for (int i = 0; i < n; i++) {  
      cnt[a[i]]++;  
      if (i == 0 || a[i] != a[i - 1]) {  
         can[i] = 1;  
         fenw.update(i, 1);  
      }  
   }  
  
   vi pmul(n + 1, 1);  
   for (int i = 0; i < n; i++) {  
      pmul[i + 1] = md(pmul[i] * (can[i] ? cnt[a[i]] : 1));  
   }  
   auto query = [&](int l, int r) {  
      return md(pmul[r + 1] * inverse(pmul[l]));  
   };  
  
   int ans = 0;  
   for (int i = 0; i < n; i++) {  
      if (!can[i]) continue;  
      int low = a[i];  
      int upper = a[i] + m - 1;  
      int j = ubi(a, upper) - 1;  
  
      int can_cnt = fenw.query(i, j);  
      if (can_cnt < m) continue;  
      ans = md(ans + query(i, j));  
   }  
   cout << ans << endl;  
}

G. Ksyusha and Chinchilla

G. Ksyusha and Chinchilla

귀찮은 구현문제인데, 에디토리얼은 tree dp를 말하지만, 다음과 같은 방법으로 풀었다.

결국 branch는 두 가지 형태만 나옴에 유의한다.

image.png

이처럼 각 브랜치의 노드마다 번호를 매겨보자.

처음에 모든 leaf 노드는 항상 $3$을 갖는다.

이제 자신이 $3$이라면 자신의 바로 부모는 $2$여야 하는 제약이 생긴다.

자신이 $2$인데 자식 중 $3$의 개수가 $2$개라면 그 두개와 자신으로 하나의 branch를 만들고, $1$개라면 그 하나와 자신의 부모와 branch를 만든다.

그 이외의 경우는 실패이다.

자신이 $1$이라면 자식 중 $3$은 없어야한다.

자신이 $1,2,3$ 셋다 아니라면 다시 자신을 $3$으로 만들어주고 leaf노드처럼 시작해주면 된다.

이처럼 과정을 진행후 모순이 없다면, branch에 쓰인 간선들을 모두 제외하면 잘라내야할 간선들만 남게된다.

void solve() {
   int n;
   cin >> n;
   vvi edges(n);
   map<pi, int> edge_idx;
   for (int i = 0, u, v; i < n - 1; i++) {
      cin >> u >> v, u--, v--;
      edge_idx[mp(min(u, v), max(u, v))] = i;
      edges[u].pb(v), edges[v].pb(u);
   }
   if (n % 3) {
      cout << -1 << endl;
      return;
   }
   int cut = n - 1 - n / 3 * 2;
   debug(n, cut);
   vi par(n), sub_size(n), in(n), type(n);
   vvi child(n);
   queue<int> q;
   function<void(int, int)> fn = [&](int i, int p) -> void {
      sub_size[i] = 1;
      par[i] = p;
      int c = 0;
      for (int j: edges[i])
         if (j != p) {
            in[i]++;
            fn(j, i), child[i].pb(j);
            sub_size[i] += sub_size[j];
            c++;
         }
      if (!c) {
         type[i] = 3;
         q.push(i);
      }
   };
   fn(0, -1);
   bool fail = 0;
   vi fin(n);
   map<int, set<int>> con;
   auto connect = [&](int i, int j, int k) {
      con[i].insert(j);
      con[j].insert(i);
      con[j].insert(k);
      con[k].insert(j);
   };
   while (sz(q)) {
      int i = q.front();
      q.pop();
 
      if (i == 0) {
         if (type[i] == 2) {
            vi type3child;
            for (int c: child[i]) if (type[c] == 3) type3child.pb(c);
            if (!sz(type3child) || sz(type3child) > 2) fail = 1;
            if (sz(type3child) == 1) {
               fail = 1;
            } else {
               fin[i] = fin[type3child[0]] = fin[type3child[1]] = 1;
               connect(type3child[0], i, type3child[1]);
            }
         } else if (type[i] == 1) {
            vi type3child;
            for (int c: child[i]) if (type[c] == 3) type3child.pb(c);
            if (sz(type3child)) fail = 1;
         }
         continue;
      }
      int p = par[i];
 
      if (type[i] == 3) {
         if (type[p] && type[p] != 2) fail = 1;
         type[p] = 2;
 
      } else if (type[i] == 2) {
         vi type3child;
         for (int c: child[i]) if (type[c] == 3) type3child.pb(c);
         if (!sz(type3child) || sz(type3child) > 2) fail = 1;
         if (sz(type3child) == 1) {
            if (type[p]) fail = 1;
            type[p] = 1;
            fin[i] = fin[type3child[0]] = fin[p] = 1;
            connect(type3child[0], i, p);
         } else {
            fin[i] = fin[type3child[0]] = fin[type3child[1]] = 1;
            connect(type3child[0], i, type3child[1]);
         }
      } else if (type[i] == 1) {
         vi type3child;
         for (int c: child[i]) if (type[c] == 3) type3child.pb(c);
         if (sz(type3child)) fail = 1;
      } else {
         type[i] = 3;
         if (type[p] && type[p] != 2) fail = 1;
         type[p] = 2;
      }
 
      in[p]--;
      if (in[p] == 0) q.push(p);
   }
   debug(fin);
   for (int i = 0; i < n; i++) if (!fin[i]) fail = 1;
 
   if (fail) cout << -1 << endl;
   else {
      vi ans;
      cout << cut << endl;
      function<void(int)> fn = [&](int i) -> void {
         for (int c: child[i]) {
            if (!hass(con[i], c)) {
               ans.pb(edge_idx[mp(min(i, c), max(i, c))]);
            }
            fn(c);
         }
      };
      fn(0);
      for (auto i: ans) cout << i + 1 << ' ';
      cout << endl;
   }
}
 

에디토리얼 풀이 1

$dpc_v=$ 서브트리 $v$에서 모든 자식으로 가는 간선을 자를 수 있다

$dpo_v=$ 서브트리 $v$에서 모든 자식으로 가는 간선 중 하나만 남기고 자를 수 있다.

$dp_v=$ 서브트리 $v$에서 부모로 가는 간선을 자를 수 있다.

처럼 정의하고 트리 DP를 쓰는 것이다.

명백히 해의 존재는 $dp_0$ 이 참일 경우이다.

에디토리얼 풀이 2

그리디로 풀 수 있다.

light vertex를 leaf가 아니고 자신의 모든 자식이 leaf인 vertex라고 정의한다.

heavy vertex를 leaf가 아니고 light vertex인 자식을 가지는 vertex라고 정의한다.

만약 light vertex 중 자식이 세 개 이상인 것이 있다면 정답은 존재하지 않는다.

만약 light vertex가 오직 하나의 자식을 갖는다면 우린 그것의 부모에서 light vertex만 남기고 모두 자른다.

만약 light vertex가 두 개의 자식을 갖는다면 light vertex에서 부모로 가는 간선을 자른다.

우선, 각 vertex의 자식의 개수와 light vertex인 자식의 개수를 센다.

light vertex를 계속 자르다보면 부모도 light vertex가 되는데, 그럼 큐에 넣는다.

이렇게 어쩌구 저쩌구해서 정답을 구할 수 있다는 것 같은데 내 풀이와 유사한 것 같다.

Comments