Codeforces Round 873 (Div. 2) A~D2

发布时间 2023-05-21 17:16:59作者: xhy666

Codeforces Round 873 (Div. 2) A~D2

A. Divisible Array

因为\(1\)一定整除,构造\(a_i=i\),再让\(a_1\)加上和模\(n\)的余数

void work() {  
	int n;
	cin >> n;
	vector<int> a(n + 1);
	int s = 0;
	rep (i, 1, n) a[i] = i, s += i;
	a[1] += s % n;
	rep (i, 1, n) cout << a[i] << " \n"[i == n];
}

B. Permutation Swap

对于\(p_i\)肯定要移动到位置\(i\),那么移动的步数一定能整除\(abs(p_i-i)\)

void work() {  
	int n;
	cin >> n;
	vector<int> a(n + 1), b(n + 1);
	rep (i, 1, n) cin >> a[i], b[a[i]] = i;
	
	int res = 0;
	rep (i, 1, n) res = __gcd(res, abs(b[i] - i));
	cout << res << "\n";
}

C. Counting Orders

排序不影响答案统计,从大到小排序后,第\(i\)个位置能选择的数一定是第\(i+1\)个位置能选的子集

void work() {  
	int n;
	cin >> n;
	vector<int> a(n + 1), b(n + 1), s(n + 1);
	rep (i, 1, n) cin >> a[i];
	rep (i, 1, n) cin >> b[i];
	sort(a.begin() + 1, a.end());
	sort(b.begin() + 1, b.end());
	
	int pos = 1;
	rep (i, 1, n) {
		while (pos <= n && a[pos] <= b[i]) pos++;
		s[i] = n - pos + 1;
	} 
	
	int res = 1;
	rrep (i, n, 1) {
		int p = max(0, s[i] - (n - i));
		res = 1ll * res * p % MOD;
	}
	cout << res << "\n";
}

D1. Range Sorting (Easy Version)

选择的两两区间一定不相交,如果相交用它们的并区间替换它们只会使答案更优。

\(i\)个数加入后贡献了\(i\)个区间,对于区间\([l, i](1 \le l\le i)\),最终排序后\(a_i\)的位置为\(k\),那么\([k, i]\)这个区间一定是要被包含的,试想能否单独考虑\([l,k-1]\)这个区间,如果可以就能用\(dp\)转移了,可以发现只有当\(max(a_l,a_{l+1},...,a_{k-1})<min(a_k,a_{k+1},...,a_i)\)时才成立,所以我们在扩大区间时顺便维护最大的\(lpos\),使得\(max(a_l,a_{l+1},...,a_{lpos-1})<min(a_{lpos},a_{lpos+1},...,a_i)\)。那么区间\([l,i]\)的贡献就是\(dp[l][lpos-1]+i-lpos\),容易证明,如果分界点在\([lpos, k]\)之间,就会有区间相交的情况出现

时间复杂度\(O(n^2)\)

void work() {  
	int n;
	cin >> n;
	vector<int> a(n + 1);
	vector<vector<int>> dp(n + 1, vector<int>(n + 1));
	rep (i, 1, n) cin >> a[i];
	
	rep (i, 1, n) dp[i][i] = 0;
	LL res = 0;
	rep (i, 1, n) {
		int nxtmn = 1e9 + 7, lpos = i, mn = a[i];
		rrep (j, i - 1, 1) {
			if (a[j] > mn) {
				lpos = j;
				mn = min(nxtmn, mn);	
				nxtmn = 1e9 + 7;
			}
			else nxtmn = min(nxtmn, a[j]); 
			dp[j][i] = dp[j][lpos - 1] + i - lpos;
			res += dp[j][i];
		}
	}
	cout << res << "\n";
}

D2. Range Sorting (Hard Version)

对于每个连续子序列最终的形态,一定是若干个两两不相交的区间。设连续子序列\([l,r]\),按照\(max(a[l...k])<min(a[k+1...r])\)可以划分为\([l,k_1],[k_1+1,k_2],...,[k_m+1,r]\),设最终满足最小代价的区间划分为\([l,p_1],[p_1+1,p_2]...[p_t+1,r]\)

可以证明\(m=t\)。因为有一个满足条件的\(k\)就意味着代价减一,也就对应着一个不同的\(p\),所以\(m<=t\);而每个\(p\),都有\(max(a[l,p])<min(a[p+1,r])\)(排序后严格递增),所以\(t<=m\)。综上\(m=t\)

那么总代价就是\((l,r,k)\)三元组的数量

接下来就是找一种划分方式,使得程序能够在合适的时间复杂度下找出所有这样的三元组,一种方式就是枚举\(a_i\)作为\(min(a[k+1...r])\),感觉这步是最难想的,能想到就能做了。

\(x\)是最大的满足\(x<i\)\(a_x<a_i\)\(z\)是最大的满足\(z<x\)\(a_z>a_i\)\(y\)是最小的满足\(y>i\)\(a_y<a_i\),那么这部分减少的代价就是\((x-z)*(y-i)\)

对于每个\(i\)\(x,y\)可以用单调栈预处理出来,\(z\)可以用二分+st表求出。总的时间复杂度\(O(nlognlogn)\)

int n, a[N];
int st[N][21];
 
int query(int l, int r) {
	if (r - l + 1 <= 0) return -1;
	int k = log2(r - l + 1);
	return max(st[l][k], st[r - (1 << k) + 1][k]);
}
 
void init_st() {
	for (int i = 1; i <= n; i++) st[i][0] = a[i];
	for (int j = 1; j <= 20; j++) {
		for (int i = 1; i + (1 << j) - 1 <= n; i++) {
			st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
		}
	}
}
 
void work() {  
	cin >> n;
	rep (i, 1, n) cin >> a[i];
	init_st();
	
	vector<int> kpos(n + 1), ypos(n + 1, n + 1);
	stack<int> stk;
	rep (i, 1, n) {
		while (stk.size() && a[i] < a[stk.top()]) {
			ypos[stk.top()] = i;
			stk.pop();
		}
		stk.push(i); 
	}
	while (stk.size()) stk.pop();
	rrep (i, n, 1) {
		while (stk.size() && a[i] < a[stk.top()]) {
			kpos[stk.top()] = i;
			stk.pop();
		}
		stk.push(i);
	}
	
	LL res = 0;
	rep (i, 1, n) res += 1ll * i * (i - 1) / 2; 
	rep (i, 1, n) {
		int l = 0, r = kpos[i] - 1;
		while (l < r) {
			int mid = l + r + 1 >> 1;
			if (query(mid, kpos[i] - 1) > a[i]) l = mid;
			else r = mid - 1;
		}
		res -= 1ll * (kpos[i] - l) * (ypos[i] - i);
	}
	cout << res << "\n";
}