E. Between

관찰을 통해 풀 수 있었다.

대회는 아니지만 2200 자력솔은 최근 다시 코포 공부를 시작하고 처음하는듯하다.

Observation 1. 2 ~ n중 어떤 수가 a[i] 에 나오지 않는다면 길이는 무한하다.

1을 둔다음 그 수만 무한히 붙이면 되기 때문이다.

Observation 2

숫자 $x$가 수열에 나오는 횟수를 $f(x)$라고 하자. $f(1)=1$ 이다.

$b_i$ 라는 수가 $k$ 번 나오면, $a_i$는 최대 $k+1$ 번 나올 수 있다.

$1$ 이라는 수가 $1$ 번 나온다는 조건 덕에 우리는 BFS를 시행할 수 있다.

처음에 $2 \to n$ 의 수가 나올 수 있는 횟수를 $\infty$로 초기화하고 $f(1)=1$ 로만 해두고 BFS를 돌린다.

$b_i \to a_i$ 로의 모든 간선을 고려하여 $f(a_i)$ 가 $f(b_i)+1$ 보다 많이 나올 수 있다고 체크되어있을 때 $f(a_i) \coloneqq f(b_i)+1$ 해주고 $a_i$를 큐에 넣음으로 모든 수들의 $f$ 값을 구할 수 있다.

여기서 $f$ 값이 $\infty$인 수가 있다면 그 수를 무한히 붙여 수열을 만들 수 있다는 의미이므로 정답은 무한하다.

이제 모든 수의 $f$ 값을 가지고 정답을 어떻게 구성할 것인지가 문제이다.

내 Construction

fn(l, r) 을 $l \sim r$ 구간에서 숫자들을 채워넣는 재귀함수라고 하자.

남은 숫자들 중 가장 여러번 나와야 하는 수들의 집합 $S$ 를 고려한다.

$f(S_i)=1$ 이라면 그냥 모든 수를 $1 \to n$ 으로 훑으며 순서대로 $f(i) = 1$ 일 경우 넣어주고 끝낸다.

if (cnt[mxi] == 1) {  
   for (int i = l, j = 1; i <= r; i++) {  
      while (!cnt[j])j++;  
      ans[i] = j;  
      cnt[j]--;  
   }  
   return;  
}

그렇지 않다면 $S$에 있는 수들은 나와야 할 횟수가 $2$ 이상인 것들이므로, $S=\{1,2,3\}$ 이라 한다면

$1,2,3,[\dots],1,2,3$ 처럼 구간을 채우고 중간 부분은 다시 재귀호출한다.

이 방법이 성립하는 이유는 다음과 같다.

$a_i, b_i$가 있다고 해보자.

일단 $1,2,3$ 중 $a_i$ 에 존재하는 것이 있다면 $b_i$ 가 무엇이여도 항상 그것들의 사이에 들어가게 분배된다.

자세히 설명은 못하겠는데, 에디토리얼도 대략 이런 방식으로 순서대로 끼워넣어서 만드는것같다.

void solve() {  
   int n, m;  
   cin >> n >> m;  
   vector<pi> p;  
   vi a_has(n + 1);  
   vvi ba(n + 1), ab(n + 1);  
   for (int i = 0; i < m; i++) {  
      int a, b;  
      cin >> a >> b;  
      if (a == 1) continue;  
      a_has[a] = 1;  
      p.pb({a, b});  
      ba[b].pb(a);  
      ab[a].pb(b);  
   }  
   for (int i = 1; i <= n; i++) sort(all(ab[i]));  
   m = sz(p);  
   if (n == 1) {  
      cout << "FINITE\n1\n1\n";  
      return;  
   }  
   vi possible(n + 1, 1e9);  
   possible[1] = 1;  
   queue<int> q;  
   q.push(1);  
   while (sz(q)) {  
      int b = q.front();  
      q.pop();  
  
      for (int a: ba[b]) {  
         if (possible[a] > possible[b] + 1) {  
            possible[a] = possible[b] + 1;  
            q.push(a);  
         }  
      }  
   }  
   debug(possible);  
   int sum = 1;  
   for (int i = 2; i <= n; i++) {  
      if (possible[i] == 1e9) {  
         cout << "INFINITE\n";  
         return;  
      }  
      sum += possible[i];  
   }  
   cout << "FINITE\n" << sum << '\n';  
   vi ans(sum + 1);  
   vi cnt = possible;  
   function<void(int, int)> fn = [&](int l, int r) -> void {  
      int mxi = 1, sum = 0;  
      for (int i = 1; i <= n; i++) {  
         sum += cnt[i];  
         if (cnt[i] > cnt[mxi]) mxi = i;  
      }  
      assert(r - l + 1 == sum);  
  
      if (cnt[mxi] == 1) {  
         for (int i = l, j = 1; i <= r; i++) {  
            while (!cnt[j])j++;  
            ans[i] = j;  
            cnt[j]--;  
         }  
         return;  
      }  
  
      vi mx_list;  
      for (int i = 1; i <= n; i++) {  
         if (cnt[i] == cnt[mxi]) mx_list.pb(i);  
      }  
      int L = sz(mx_list);  
  
      for (int i = l; i < l + L; i++) ans[i] = mx_list[i - l];  
      for (int i = r - L + 1; i <= r; i++) ans[i] = mx_list[i - (r - L + 1)];  
      for (int i: mx_list) cnt[i] -= 2;  
      fn(l + L, r - L);  
   };  
   fn(1, sum);  
   for (int i = 1; i <= sum; i++) cout << ans[i] << ' ';  
   cout << endl;  
}

Comments