Codeforces Round #842 (Div. 2)

A. Greatest Convex

题意:

给定 k k k,求出最大的 x ( x < k ) x(x<k) x(x<k) ,满足 x ! + ( x − 1 ) ! x!+(x-1)! x!+(x1)! k k k 的倍数

解析:

x ! + ( x − 1 ) ! = x ( x − 1 ) ! + ( x − 1 ) ! = ( x + 1 ) ( x − 1 ) ! x!+(x-1)! = x(x-1)!+(x-1)! = (x+1)(x-1)! x!+(x1)!=x(x1)!+(x1)!=(x+1)(x1)!

容易发现,当 x = k − 1 x = k-1 x=k1 时, x ! + ( x − 1 ) ! = k ( k − 2 ) ! x!+(x-1)! = k(k-2)! x!+(x1)!=k(k2)! 。此时 x x x 为最大值且符合条件。

因此,答案为 k − 1 k-1 k1

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int T;
	cin >> T;
	while(T--){
		int k;
		cin >> k;
		cout << k-1 << endl; 
	}
	return 0;
}


B. Quick Sort

题意:

给定 n n n 的一个排列,每次操作选择 k k k 个数,排序后放在序列尾部,询问使序列有序的最少操作次数

解析:

如果有 x x x 个数从来没有被选中,则这 x x x 个数的相对位置关系不变,此时操作次数为 ⌈ n − x k ⌉ \lceil \frac{n-x}{k}\rceil knx
贪心的考虑,应该使 x x x 尽可能的大,即序列中的子序列 1 , 2 , 3... 1,2,3... 1,2,3... 的长度。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int n, k, a[maxn], pos[maxn];
void solve(){
	cin >> n >> k;
	for(int i = 0; i <= n; i++)
		a[i] = pos[i] = 0;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
		pos[a[i]] = i;
	}
	int cnt = 0;
	for(int i = 1; i <= n; i++){
		if(pos[i] > pos[i-1])
			cnt++;
		else
			break;	
	}
	int ans = ceil(1.0*(n-cnt)/k);
	//cout << "ans = ";
	cout << ans << endl; 
}
int main(){
	int T;
	cin >> T;
	while(T--)
		solve();
	return 0;
}


C. Elemental Decompress

题意:

给定序列 a a a ,构造排列 p , q p,q p,q ,使 a i = m a x ( p i , q i ) a_i = max(p_i, q_i) ai=max(pi,qi)

解析:

首先考虑排列 p , q p,q p,q 不存在的情况:

  • 一个数在序列 a a a 最多出现两次
  • a a a 升序排序后应该有 a i > = i a_i >= i ai>=i

假设存在 a i < i a_i < i ai<i,则 a 1 a_1 a1 a i − 1 a_{i-1} ai1 均小于 i i i ,则 p 1 p_1 p1 p i p_i pi q 1 q_1 q1 q i q_i qi 均小于 i i i ,即有 2 i 2i 2i 个数小于 i i i,显然是不可能的。

对于 p , q p, q p,q 存在的情况,先将 a a a 中的数填到 p p p q q q 中,然后维护每个排列可用的数。如果 p i p_i pi 没填,则在集合中找到小于等于 q i q_i qi 的数,填到 p i p_i pi

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define mkp(a, b) make_pair(a, b)
#define fi first 
#define se second 
const int maxn = 2e5+10;

int a[maxn], n;
int p[maxn], q[maxn];
int cnt[maxn];
int pvis[maxn], qvis[maxn];
vector<pii> s;
set<int> b;
void solve(){
	cin >> n;
	s.clear(); b.clear();
	for(int i = 0; i <= n; i++)
		p[i] = q[i] = cnt[i] = pvis[i] = qvis[i] = 0;
	for(int i = 1; i <= n; i++){
		int x; cin >> x; cnt[x]++;
		s.push_back(mkp(x, i));
	}
	for(int i = 1; i <= n; i++){
		if(cnt[i] > 2){
			cout << "NO" << endl;
			return;
		}
	}	
	s.push_back(mkp(0, 0));
	sort(s.begin(), s.end());
	for(int i = 1; i < s.size(); i++){
		if(s[i].fi < i){
			cout << "NO" << endl;
			return;
		}
	}
	for(int i = n; i >= 1; i--){
		int x = s[i].fi;
		int pos = s[i].se;
		if(pvis[x] == 0){
			p[pos] = x;
			pvis[x] = 1;
		}
		else{
			q[pos] = x;
			qvis[x] = 1;
		}
	}
	
	
	for(int i = 1; i <= n; i++){
		if(pvis[i] == 0)
			b.insert(i);
	}
	set<int>::iterator it;
	for(int i = n; i >= 1; i--){
		if(p[i] == 0){
			it = --b.upper_bound(q[i]);
			p[i] = *it;
			b.erase(it);
		}
	}
	
	b.clear();
	for(int i = 1; i <= n; i++){
		if(qvis[i] == 0)
			b.insert(i);
	}
	for(int i = n; i >= 1; i--){
		if(q[i] == 0){
			it = --b.upper_bound(p[i]);
			q[i] = *it;
			b.erase(it);
		}
	}
	
	cout << "YES" << endl;
	for(int i = 1; i <= n; i++){
		cout << p[i] << " ";
	}
	cout << endl;
	for(int i = 1; i <= n; i++){
		cout << q[i] << " ";
	}
	cout << endl;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	int T;
	cin >> T;
	while(T--)
		solve();
	return 0;
}


D. Lucky Permutation

题意:

给定长度为 n n n 的排列 p p p,每次操作可以交换两个数,询问最少的操作次数使序列中只有一个逆序对

解析:

首先很容易想到逆序对个数为1的排列只有 n − 1 n-1 n1 个,最终序列一定是这 n − 1 n-1 n1 中的一个。

考虑最终序列没有逆序对的情况。 i i i p i p_i pi 连边建图 G 1 G1 G1,如果最终序列没有逆序对,则操作次数为图中每个环长度-1的和,即 c n t = ∑ ( l e n i − 1 ) cnt = \sum(len_i-1) cnt=(leni1) ,即 c n t = n − c n t c cnt = n-cnt_c cnt=ncntc c n t c cnt_c cntc 为环的个数。要使操作次数最少,需要使环的数目最多

考虑最终序列只有一个逆序对的情况,设最终序列为 p ′ p' p ,则 p i ′ p'_i pi p i p_i pi 连边建图 G 2 G_2 G2。如果对于每种可能的最终序列均建一次图的话,时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。考虑没有逆序对和只有一个逆序对两张图的关系,不难发现,如果逆序对在 G 1 G_1 G1 中如果在同一个环内,会使 G 2 G_2 G2 中环的个数+1,如果不在同一个环内,会使图 G 2 G_2 G2 中环的个数-1。

因此,枚举每一种情况,判断两点是否在同一个环内。时间复杂度为 O ( n ) O(n) O(n)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5+10;
int a[maxn], c[maxn];
void solve(){
	int n;
	cin >> n;
	for(int i = 0; i <= n; i++)
		c[i] = 0;
	for(int i = 1; i <= n; i++)
		cin >> a[i];
	int cnt = 0;
	for(int i = 1; i <= n; i++){
		if(c[i])
			continue;
		int x = i;
		x = a[x];
		c[x] = i;
		while(x != i){
			x = a[x];
			c[x] = i;
			cnt++;
		}
	}
	//cout << "ans = ";
	for(int i = 1; i < n; i++){
		if(c[i] == c[i+1]){
			cout << cnt -1 << endl;
			return;
		}
	}
	cout << cnt+1 << endl;
	return;
}
int main(){
	int T;
	cin >> T;
	while(T--)
		solve();
	return 0;
}
	


E. Partial Sorting

题意:

给定长度为 3 n 3n 3n 的一个排列,每次操作可以将前 2 n 2n 2n 个元素按升序排列,或者将后 2 n 2n 2n 个元素按降序排列。询问将长度为 3 n 3n 3n 的所有排列变为升序的操作次数和,和对质数 m m m 取模

解析:

首先可以大概发现,最多操作三次,就一定可以将序列变为升序(不严谨地考虑序列降序的情况,三次就可以)。然后依次分析每个次数对应序列的个数

  • 0 次:

显然序列只有在升序的时候才需要操作0次。此时序列只有1个

  • 1 次:

操作1次就可以将序列升序,因此序列前 n n n 个数为最小的 n n n 个数且有序,或者序列后 n n n 个数为最大的 n n n 个数且有序。

满足条件的排列个数为 ( 2 n ) ! + ( 2 n ) ! (2n)!+(2n)! (2n)!+(2n)!

需要减去重复计数的序列,即序列前 n n n 个数为最小的 n n n 个数且有序,并且序列后 n n n 个数为最大的 n n n 个数且有序。

因此,操作次数为1的排列的个数为 2 ( 2 n ) ! − n ! 2(2n)! - n! 2(2n)!n!。容易发现,初始序列升序的情况也满足条件,因此 2 ( 2 n ) ! − n ! 2(2n)! - n! 2(2n)!n! 为操作次数小于等于 1 的排列个数

  • 2 次:

两次的情况是最小的 n n n 个数分布在前 2 n 2n 2n 个位置,或者是最大的 n n n 个数只分布在后 2 n 2n 2n 个位置。

序列个数为 A 2 n n × ( 2 n ) ! × 2 A_{2n}^n \times (2n)! \times 2 A2nn×(2n)!×2 A 2 n n A_{2n}^n A2nn 是将最小的 n n n 个数放到前 2 n 2n 2n 个位置, ( 2 n ) ! (2n)! (2n)! 是将剩下数放进去,最后的 2 2 2 是两种情况。 A 2 n n = C 2 n n × n ! A_{2n}^n = C_{2n}^n \times n! A2nn=C2nn×n!

需要减去重复计数的排列,即最小的 n n n 个数分布在前 2 n 2n 2n 个位置并且且最大的 n n n 个数分布在后 2 n 2n 2n 个位置。

重复计数的序列可以枚举最小的 n n n 个数中位于前 n n n 个位置中的个数 i i i i i i 个数放到前 n n n 个位置: C n i C_n^i Cni n − i n-i ni 个数放到中间 n n n 个位置: C n n − i = C n i C_n^{n-i} = C_n^i Cnni=Cni ;最大的 n n n 个数可以选择的位置有 n + i n+i n+i个: C n + i n C_{n+i}^n Cn+in。此时已经选好了位置,但还没有进行排列,最小的 n n n 个数,中间的 n n n 个数,最大的 n n n 个数均需要排列,因此对于每个 i i i,序列个数为 C n i × C n i × C n + i i × ( n ! ) 3 C_n^i \times C_n^i \times C_{n+i}^i \times (n!)^3 Cni×Cni×Cn+ii×(n!)3

重复计数序列的个数为 ∑ i = 0 n C n i × C n i × C n + i i × ( n ! ) 3 \sum\limits_{i=0}\limits^n C_n^i \times C_n^i \times C_{n+i}^i \times (n!)^3 i=0nCni×Cni×Cn+ii×(n!)3

因此,操作次数小于等于2的排列个数为 C 2 n n × n ! × ( 2 n ) ! × 2 − ∑ i = 0 n C n i × C n i × C n + i i × ( n ! ) 3 C_{2n}^n \times n! \times (2n)! \times 2-\sum\limits_{i=0}\limits^n C_n^i \times C_n^i \times C_{n+i}^i \times (n!)^3 C2nn×n!×(2n)!×2i=0nCni×Cni×Cn+ii×(n!)3

  • 3 次:

所有排列的操作次数都小于等于三次,因此排列个数为 ( 3 n ) ! (3n)! (3n)!

综上,可以求出每种操作次数对应的排列的个数。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll

const int maxn = 3e6+10;
int mod;
ll qpow(ll a, ll b){
	ll res = 1;
	while(b){
		if(b&1)
			res = res * a % mod;
		a = a * a % mod;
		b = b >> 1;
	}
	return res;
}
int fac[maxn], inv[maxn], finv[maxn];
void init(int n){
	inv[1] = 1;
	for(int i = 2; i <= n; i++)
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	fac[0] = finv[0] = 1;
	for(int i = 1; i <= n; i++){
		fac[i] = fac[i-1] * i % mod;
		finv[i] = finv[i-1] * inv[i] % mod;
	}
	
}
ll C(int a, int b){
	if(a < b || b < 0)
		return 0;
	return fac[a] * finv[a-b] % mod * finv[b] % mod;
}
signed main(){
	int n;
	cin >> n >> mod;
	init(3 * n);
	
	ll cnt0 = 1;
	ll cnt1 = 2 * fac[2*n] - fac[n];
	ll cnt2 = 2 * C(2*n, n) * fac[n] % mod * fac[2*n] % mod;
	ll cnt3 = fac[3*n];
	ll x = fac[n] * fac[n] % mod * fac[n] % mod;
	for(int i = 0; i <= n; i++){
		cnt2 -= C(n, i) * C(n, i) % mod * C(n+i, i) % mod * x % mod;
	}
	cnt2 = ((cnt2 % mod) + mod) % mod;
	cnt3 -= cnt2;
	cnt2 -= cnt1;
	cnt1 -= cnt0;
	
	ll ans = cnt3*3+cnt2*2+cnt1*1;
	ans = ((ans % mod) + mod) % mod;
	cout << ans << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>