A. Greatest Convex
题意:
给定 k k k,求出最大的 x ( x < k ) x(x<k) x(x<k) ,满足 x ! + ( x − 1 ) ! x!+(x-1)! x!+(x−1)! 是 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!+(x−1)!=x(x−1)!+(x−1)!=(x+1)(x−1)!
容易发现,当 x = k − 1 x = k-1 x=k−1 时, x ! + ( x − 1 ) ! = k ( k − 2 ) ! x!+(x-1)! = k(k-2)! x!+(x−1)!=k(k−2)! 。此时 x x x 为最大值且符合条件。
因此,答案为 k − 1 k-1 k−1
代码:
#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
⌈kn−x⌉。
贪心的考虑,应该使
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} ai−1 均小于 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 n−1 个,最终序列一定是这 n − 1 n-1 n−1 中的一个。
考虑最终序列没有逆序对的情况。 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=∑(leni−1) ,即 c n t = n − c n t c cnt = n-cnt_c cnt=n−cntc , 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 n−i 个数放到中间 n n n 个位置: C n n − i = C n i C_n^{n-i} = C_n^i Cnn−i=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=0∑nCni×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)!×2−i=0∑nCni×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;
}