Solution - Atcoder ARC114F Permutation Division

rizynvu發表於2024-07-28

\(a\) 為題目中的 \(P\)

首先考慮後手的重排策略是什麼。
因為 \(a\) 是個排列,元素互不相同,那麼肯定就是按照每一段的第一個數的大小,越大的放在越前面。

那麼當 \(a_1\le k\) 的時候,顯然先手會把以 \(1\sim k\) 開頭來劃分段。
因為否則存在一個開頭 \(> k\) 的段,後手把其放在最前面,那麼此時得到的新排列肯定開頭 \(> k\);一定不比上面提到的劃分方式(開頭為 \(k\))優。

接下來考慮 \(a_1 > k\) 的時候。
類似的,可以知道肯定不會有劃分出的段的開頭 \(> a_1\),因為這樣肯定不如就讓 \(a_1\) 開頭。
所以說每段開頭都 \(\le a_1\)

那麼考慮 \(a_1\) 所在的段 \([1, w]\),同時肯定就會有一個以 \(w + 1\) 開頭的段。
按照後手的重排策略,最終得到的排列 \(a'\) 肯定滿足 \(a_{[1, w]} = a'_{[1, w]}, a'_{w + 1}\ge a_{w + 1}\)

於是能夠發現要使的最後得到的 \(a'\) 儘量小,就需要最大化 \(\operatorname{LCP}(a, a')\),其次如果最大化了 \(\operatorname{LCP} = w\),就需要最小化 \([w + 1, n]\) 選出的段的開頭按從大到小的順序後的字典序。

先來考慮最大化 \(\operatorname{LCP}\)

考慮列舉 \(\operatorname{LCP}\) 中的最後一段,記開頭為 \(i\)
考慮如果選出了 \(a_{j_1} > a_{j_2} > \cdots > a_{j_m}(1 = j_1 > j_2 > \cdots > j_m = i)\),那麼以 \(j_{1\sim k}\) 都成為段的開頭,一定是不會影響最終的順序的。
那麼就還需要在 \([i + 1, n]\) 確定出 \(k - m\) 個開頭,因為要最大化 \(\operatorname{LCP}\),所以顯然會去選最後的 \(k - m\)\(< a_i\) 的數(當然需要滿足這些數的位置 \(> i\))。

於是就可以知道需要最大化這個 \(m\)
那麼對 \(a\) 跑一個以 \(a_1\) 開頭的最長下降子序列即可求出對於每個 \(i\)\(m\)

接下來考慮最小化 \([w + 1, n]\) 選出的段的開頭按從大到小的順序後的字典序。
根據上文提到的,可以知道,\(w + 1\) 後選擇的開頭就是 \(k - m\)\(< a_i\) 的數。
又因為最大化了 \(\operatorname{LCP}\),所以此時最後肯定會更好剩下 \(k - m\) 個數,所以說這 \(k - m\) 個數一定是 \([w + 1, n]\) 中最小的 \(k - m\) 個數。
那麼最小化 \(k - m\) 就可以了。

對於上述找到第 \(k - m\)\(< a_i\) 的數,可以考慮樹狀陣列二分得到。

知道開頭後,按照後手的方案構造即可。

時間複雜度 \(\mathcal{O}(n\log n)\)

#include<bits/stdc++.h>
const int maxn = 2e5 + 10, limn = 1 << 18;
int a[maxn], w[maxn];
int f[maxn], val[maxn];
int ed[maxn];
int tr[limn + 1];
inline void add(int x) {
   for (int i = x; i <= limn; i += i & -i)
      tr[i]++;
}
inline int qry(int k) {
   int now = limn;
   for (int i = 17; ~ i; i--)
      if (k > tr[now - (1 << i)])
         k -= tr[now - (1 << i)];
      else
         now -= 1 << i;
   return now;
}
int main() {
   int n, k; scanf("%d%d", &n, &k);
   for (int i = 1; i <= n; i++)
      scanf("%d", &a[i]), w[a[i]] = i;
   ed[n + 1] = 1;
   if (a[1] <= k) {
      for (int i = 1; i <= k; i++)
         ed[w[i]] = 1;
      for (int i = k; i; i--) {
         printf("%d ", i);
         for (int j = w[i] + 1; ! ed[j]; j++)
            printf("%d ", a[j]);
      }
      return 0;
   }
   int mx = 0;
   for (int i = 1; i <= n; i++) {
      if (a[i] > a[1])
         continue;
      f[i] = std::upper_bound(val + 1, val + n + 1, a[i], std::greater<int>()) - val;
      val[f[i]] = a[i];
      mx = std::max(mx, f[i]);
   }
   if (mx >= k) {
      for (int i = 1; i <= n; i++)
         printf("%d ", a[i]);
      return 0;
   }
   int lcp = 0, cnt = 0;
   for (int i = 1; i <= a[1]; i++) {
      int p = w[i], c = k - f[p];
      if (c < i) {
         int q = qry(i - c);
         if (q > p) {
            if (q > lcp) lcp = q, cnt = k + 1;
            if (q == lcp && c < cnt) cnt = c;
         }
      }
      add(p);
   }
   for (int i = 1; i < lcp; i++)
      printf("%d ", a[i]);
   std::vector<int> b;
   for (int i = lcp; i <= n; i++)
      b.push_back(i);
   std::sort(b.begin(), b.end(), [&](int x, int y) {return a[x] < a[y];});
   for (int i = 0; i < cnt; i++)
      ed[b[i]] = 1;
   for (int i = cnt - 1; ~ i; i--) {
      printf("%d ", a[b[i]]);
      for (int j = b[i] + 1; ! ed[j]; j++)
         printf("%d ",a[j]);
   }
   return 0;
}

相關文章