1. 程式人生 > 實用技巧 >Solution -「LOCAL」「cov. 牛客多校 2020 第三場 I」禮物

Solution -「LOCAL」「cov. 牛客多校 2020 第三場 I」禮物

\(\mathcal{Description}\)

  給定排列 \(\{a_n\}\),求字典序第 \(K\) 大的合法排列 \(\{b_n\}\)。稱一個排列 \(\{p_n\}\) 合法,當且僅當依次將 \([1,m],[2,m+1],\cdots,[n-m+1,n]\) 內的 \(p\) 升序排列後,得到的排列為 \(\{a_n\}\) 相同。

  \(n\le2 \times 10^6\)\(m\le 100\)\(K\le2 \times 10^{16}\)

\(\mathcal{Solution}\)

  應該說是構造題吧,想到幾乎所有結論卻打不出分 qwq。

  顯然,\(b_i\)

\(\{a_n\}\) 中的下標屬於集合 \([\max\{1,i-m+1\},n]\),反過來,\(a_i\)\(\{b_n\}\) 中對應的下標屬於集合 \([1,\min\{i+m-1,n\}]\)

  然後可以發現 \(\{a_n\}\) 中的逆序對非常特殊。有性質:

\[ (\exists j\in[1,i))(a_i<a_j) \Rightarrow b_{i+m-1}=a_i \]

  歸納證明。考慮一對 \((i,j)\),滿足 \(\max_{k\in(i,j)}\{a_k\}<a_j<a_i\),若 \(i+1=j\),顯然;否則對於 \(k\in(i,j)\)

,已有 \(b_{k+m-1}=a_k\),不妨設 \(b_x=a_j\),則 \(x\not\in[i+m,j+m-2]\),而 \(x\in[1,\min\{j+m-1,n\}]\),又有 \(x\ge i+m\),所以 \(x=j+m-1\) 成立。

  所以可以直接確定所有存在逆序對的 \(j\) 的位置。接下來考慮 \(\{a_n\}\) 是一個單增排列的情況。

  從左往右構造 \(\{b_n\}\),我們需要求出固定 \(\{b_n\}\) 的字首時所有合法 \(\{b_n\}\) 的方案數。不妨設固定前 \(i\) 位,對於一個沒有出現的 \(a_j\),其放置方案數顯然為 \(\min\{j+m-1,n\}-i\)

。乘法原理就可以得到所求,最後類似多叉樹求第 \(K\) 大地列舉就好,複雜度 \(\mathcal O(n^3)\)

  由於方案數是指數增長,所以前面很多位直接從小到達欽定,再對字尾的 \(\mathcal O(\log_mn)\) 暴力構造即可。

  複雜度 \(\mathcal O(n+\log^3n)\)

\(\mathcal{Code}\)

/* Clearink */

#include <cstdio>
#include <algorithm>

typedef long long LL;

inline LL rint () {
	LL x = 0; char s = getchar ();
	for ( ; s < '0' || '9' < s; s = getchar () );
	for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
	return x;
}

template<typename Tp>
inline void wint ( Tp x ) {
	if ( x < 0 ) putchar ( '-' ), x = ~ x + 1;
	if ( 9 < x ) wint ( x / 10 );
	putchar ( x % 10 ^ '0' );
}

inline int min_ ( const int a, const int b ) { return a < b ? a : b; }

const int MAXN = 2e6;
int n, m, a[MAXN + 5];
LL K;
int top, stk[MAXN + 5], ans[MAXN + 5];
bool used[MAXN + 5];

inline void setnxt ( int& pos, const int x ) { for ( ; ans[pos]; ++ pos ); ans[pos] = x; }

int main () {
	freopen ( "gift.in", "r", stdin );
	freopen ( "gift.out", "w", stdout );
	n = rint (), m = rint (), K = rint ();
	for ( int i = 1; i <= n; ++ i ) a[i] = rint ();
	for ( int i = 1; i <= n; ++ i ) {
		if ( a[i] < a[stk[top]] ) ans[i + m - 1] = a[i];
		else stk[++ top] = i;
	}
	int mut = top, pos = 1;
	for ( LL all = 1; mut && all < K; -- mut ) {
		all *= min_ ( m, top - mut + 2 );
	}
	for ( int i = 1; i < mut; ++ i ) setnxt ( pos, a[stk[i]] );
	for ( int i = mut; i <= top; ++ i ) {
		for ( int j = mut; j <= top; ++ j ) {
			if ( used[j] ) continue;
			used[j] = true;
			LL all = 1;
			for ( int k = mut, pre = i; k <= top; ++ k ) {
				if ( !used[k] ) {
					all *= min_ ( top, k + m - 1 ) - pre ++;
				}
			}
			if ( all < K ) K -= all, used[j] = false;
			else { setnxt ( pos, a[stk[j]] ); break; }
		}
	}
	for ( int i = 1; i <= n; ++ i ) {
		wint ( ans[i] );
		putchar ( i ^ n ? ' ' : '\n' );
	}
	return 0;
}