CF1470E Strange Permutation


CF1470E Strange Permutation

題目大意

題目鏈接

給出一個 \(1\)\(n\) 的排列 \(p_{1\dots n}\)。你可以選擇若干個互不重疊的區間,並將它們翻轉,稱為一組翻轉操作。翻轉一個區間 \([l,r]\) 的代價是 \(r - l\)。一組翻轉的代價是所選區間的代價之和。你希望花費的代價不超過 \(c\)

每組可能的翻轉操作,都會得到一個排列。考慮其中本質不同的排列,將這些排列按字典序從小到大排序。

你需要回答 \(q\) 次詢問。每次詢問有兩個參數 \(i,j\),表示問字典序第 \(j\) 小的排列里,第 \(i\) 個位置上的數是幾。如果不存在 \(j\) 個排列,則輸出 \(-1\)

數據范圍:\(1\leq n\leq 3\times10^4\)\(1\leq c\leq 4\)\(1\leq q\leq 3\times 10^5\)

本題題解

首先,因為操作互不重疊,且原序列是個排列,所以得到的結果序列一定互不相同。那么一組翻轉操作,就唯一對應一個結果序列。問題從找字典序第 \(j\) 小結果序列,轉化為找排序后第 \(j\) 個翻轉操作組。

考慮一個長度為 \(n\) 的序列,進行總代價不超過 \(c\) 的一組翻轉操作,能得到多少種結果?枚舉總代價 \(i\) (\(0\leq i\leq c\))。在序列每兩個元素之間放一個小球(共 \(n - 1\) 個小球)。則一次操作的代價,就是區間里的小球數。所以恰好進行 \(i\) 次操作的方案數就是 \({n - 1\choose i}\)。總代價不超過 \(c\) 的方案數,就是 \(\text{ways}(n, c) = \sum_{i = 0}^{c}{n - 1\choose i}\)。若詢問的 \(j\) 大於這個數,則可以直接輸出 \(-1\)

\(F(i, c, k)\),表示僅考慮 \([i,n]\) 這段后綴,花費的總代價不超過 \(c\) 的,(結果序列的字典序)第 \(k\) 小的翻轉操作組中的,第一個翻轉操作(換句話說它返回一組 \((l,r)\) 表示這個操作)。

為了快速實現 \(F\) 函數,我們先預處理一個列表 \(L(i, c)\)。表示 \([i, n]\) 這段后綴,花費的總代價不超過 \(c\),按得到的序列的字典序小到大排序的,“第一次操作”的序列。即:序列里每個元素,是一個區間,表示對這個區間進行翻轉操作,以這個翻轉操作為第一次操作翻轉操作組。假設翻轉操作為 \((l,r)\),則這樣的翻轉操作組數量就是 \(w = \text{ways}(n - r, c - (r - l))\)。把這樣的 \((w, l, r)\) 作為一個元素存在 \(L\) 中。即:\(L(i, c) = \{(l_1, r_1, w_1), (l_2,r_2, w_2),\dots,(l_k, r_k, w_k)\}\)。那么求 \(F(i, c, k)\) 時,我們只需要在 \(L(i, c)\) 序列里二分出最小的 \(j\),滿足 \(w_1 + w_2 + \dots + w_j \geq k\),則 \(F(i, c, k) = (l_j,r_j)\)

考慮求 \(L\)。按 \(i\) 從大到小遞推。

\(i + 1\) 推到 \(i\) 時,只需要插入以 \(i\) 為左端點的翻轉操作。即 \((i, i + j)\) (\(1\leq j\leq \min(c, n - i)\)),這樣的操作最多不超過 \(c\) 個。暴力枚舉這些操作。考慮原來在 \(L(i + 1, c)\) 里的操作,它們對應的序列的第一個元素,現在都是 \(p_i\)。而 \((i, i + j)\) 對應的序列的第一個元素,是 \(p_{i + j}\)。因為 \(p_{i + j}\neq p_i\),所以 \((i, i + j)\) 這個操作,要么插入到原來所有操作的左邊,要么插入到原來所有操作的右邊

於是我們用一個 \(\texttt{deque}\) 來維護,即可推出 \(L(1, c)\)。在 \(L(1, c)\) 上找到一個連續的區間,即可得到 \(L(i, c)\)。從構造過程不難看出 \(L(1, c)\) 的長度是 \(\mathcal{O}(nc)\) 的。

預處理出 \(L\) 后,可以通過二分,在 \(\mathcal{O}(\log(nc))\) 的時間內實現 \(F\) 函數。通過調用最多不超過 \(c\)\(F\) 函數,可以回答一個詢問。

時間復雜度 \(\mathcal{O}(nc^2 +qc\log (nc))\)

參考代碼

實際提交時建議加上讀入、輸出優化。詳見本博客公告。

// problem: CF1470E
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXC = 4;
const int MAXN = 3e4;

int n, c, q, a[MAXN + 5];

ll ways_eq(int len, int c) {
	// 長度為 len, 操作代價和恰好為 c
	// comb(len - 1, c)
	if (len <= 1) {
		return c == 0;
	}
	if (len - 1 < c) {
		return 0;
	}
	
	ll res = 1;
	for (int i = len - 1; i >= len - c; --i) {
		res *= i;
	}
	for (int i = c; i > 1; --i) {
		res /= i;
	}
	return res;
}
ll ways_leq(int len, int c) {
	// 長度為 len, 操作代價和小於或等於 c
	ll res = 0;
	for (int i = 0; i <= c; ++i) {
		res += ways_eq(len, i);
	}
	return res;
}

struct Node {
	int l, r;
	ll w; // 后面的操作方案數
	
	Node() {}
	Node(int _l, int _r, ll _w) {
		l = _l;
		r = _r;
		w = _w;
	}
};

Node dq[MAXC + 1][MAXN * MAXC * 2 + 10];
int ql[MAXC + 1], qr[MAXC + 1];
int sum[MAXC + 1][MAXN + 5];
ll sumw[MAXC + 1][MAXN * MAXC + 5];

bool cmp(Node lhs, Node rhs) {
	return a[lhs.r] < a[rhs.r];
}

Node F(int st, int s, ll rank) {
	int i = sum[s][st]; // 在 st 之前的, 一共有這么多區間
	/*
	// 暴力查找
	ll cur = 0;
	for (i = ql[s] + i; i <= qr[s]; ++i) {
		cur += dq[s][i].w;
		assert(cur == sumw[s][i - ql[s] + 1] - sumw[s][sum[s][st]]);
		if (cur >= rank) {
			return Node(dq[s][i].l, dq[s][i].r, cur - dq[s][i].w);
		}
	}
	*/
	
	int l = i + 1, r = qr[s] - ql[s] + 2;
	while (l < r) {
		int mid = (l + r) >> 1;
		if (sumw[s][mid] - sumw[s][i] >= rank) {
			r = mid;
		} else {
			l = mid + 1;
		}
	}
	assert(l <= qr[s] - ql[s] + 1);
	return Node(dq[s][ql[s] + l - 1].l, dq[s][ql[s] + l - 1].r, sumw[s][l - 1] - sumw[s][i]);
}

void solve_case() {
	cin >> n >> c >> q;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int s = 1; s <= c; ++s) { // 總代價小於或等於 s
		
		
//		cerr << "-------- maxcost " << s << " --------" << endl;
		ql[s] = MAXN * MAXC + 5, qr[s] = MAXN * MAXC + 4; // 隊列清空
		for (int i = 1; i <= n; ++i)
			sum[s][i] = 0;
		
		dq[s][++qr[s]] = Node(n, n, 1); // 什么都不翻轉
		for (int i = n - 1; i >= 1; --i) {
			int dl = 0, dr = 0;
			for (int j = 1; j <= min(s, n - i); ++j) {
				// 翻轉區間 [i, i + j]
				ll w = ways_leq(n - (i + j), s - j);
				
				if (a[i + j] < a[i]) {
					// 翻轉后是 a[i + j], 不翻轉是 a[i], 兩者比一比
					// 翻轉后更小, push_front
					dq[s][ql[s] - (++dl)] = Node(i, i + j, w);
					sum[s][i + 1]++;
				} else {
					dq[s][qr[s] + (++dr)] = Node(i, i + j, w);
				}
			}
			
			if (dl) {
				sort(dq[s] + ql[s] - dl, dq[s] + ql[s], cmp);
				ql[s] -= dl;
			}
			if (dr) {
				sort(dq[s] + qr[s] + 1, dq[s] + qr[s] + dr + 1, cmp);
				qr[s] += dr;
			}
		}
//		cerr << "print queue: " << endl;
//		for (int i = ql[s]; i <= qr[s]; ++i) {
//			cerr << dq[s][i].l << " " << dq[s][i].r << " " << dq[s][i].w << endl;
//		}
//		cerr << "queue end" << endl;
		
		for (int i = 1; i <= n; ++i) {
			sum[s][i] += sum[s][i - 1];
		}
		for (int i = ql[s]; i <= qr[s]; ++i) {
			sumw[s][i - ql[s] + 1] = sumw[s][i - ql[s]] + dq[s][i].w;
		}
	}
	
	ll lim = ways_leq(n, c);
	for (int tq = 1; tq <= q; ++tq) {
		int pos;
		ll rank;
//		cerr << "-------- query: " << endl;
		cin >> pos >> rank;
		
		if (rank > lim) {
			cout << -1 << endl;
			continue;
		}
		
		vector<pii> revs;
		int p = 1;
		int s = c;
		while (1) {
			Node t = F(p, s, rank);
			
//			cerr << "** " << t.l << " " << t.r << " " << t.w << endl;
			
			revs.push_back(make_pair(t.l, t.r));
			
			assert(t.w < rank);
			rank -= t.w;
			s -= (t.r - t.l);
			p = t.r + 1;
			
			if (!s)
				break;
			if (p > n)
				break;
		}
		
		/*
		// 暴力翻轉
		static int aa[MAXN + 5];
		for (int i = 1; i <= n; ++i) {
			aa[i] = a[i];
		}
		for (int i = 0; i < SZ(revs); ++i) {
			reverse(aa + revs[i].fi, aa + revs[i].se + 1);
		}
		cout << aa[pos] << endl;
		*/
		
		bool flag = 0;
		for (int i = 0; i < SZ(revs); ++i) {
			if (revs[i].fi <= pos && revs[i].se >= pos) {
				cout << a[revs[i].se - (pos - revs[i].fi)] << endl;
				flag = 1;
				break;
			}
		}
		if (!flag) {
			cout << a[pos] << endl;
		}
	}
}
int main() {
	int T; cin >> T; while (T--) {
		solve_case();
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM