A. Greatest Convex
![](https://img-blog.csdnimg.cn/img_convert/81202dbfc947af6f33a13a4d0a291374.png)
题意:
给t组数据,每组数据给定一个k,求出在[1,k)范围内,满足x!+(x-1)!%k==0的最大x
题解:
赛中可以通过观察法得知输出k-1即可,赛后可以尝试证明
实际上对于x!+(x-1)!,取x为k-1,则有x!+(x-1)!=1*2*3*...*(k-2)*(k-1)+1*2*3*...*(k-2)
不难发现提取公因式后,原式可以化简为:x!+(x-1)!=1*2*3*...*(k-2)*k
此式一定可以被k整除,所以每组数据直接输出k-1即可。
本题代码:
过于简单所以略。
B. Quick Sort
![](https://img-blog.csdnimg.cn/img_convert/e7c6903c3cdfd497b0fddac2ed718a7a.png)
题意:
给t组数据,每组数据给定一个n,一个k和一个长度为n的乱序的排列,你每次可以选择k个元素将他们从数组中拿出,并且排好序放在数组的最后,求让数组升序的最小操作次数。
题解:
由题可知,每次操作都会把元素移出并且放到最后,不难想到一种贪心策略:
从1开始,根据原始顺序有序的最长序列可以不用动(中间可以隔有其他元素)。
如n=8,数组为{1,6,4,2,7,8,3,5}时,最长序列就为{1,2,3}
数组中的1,2,3元素是可以不用操作的,移出其他乱序的元素,1,2,3就会自然有序,利用反证法,如果移动其中任一元素,比如1,所以可以得到如下算法:
记录每个元素的下标mp[i],找到最长的有序串,即最大的i(记为pos)使得mp[i]>mp[i-1],那么需要操作的元素个数就为n-pos个,操作次数就为(n-pos+k-1)/k。
本题代码:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<cstring>
#include<set>
#include<queue>
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const int N = 5E5 + 7;
using namespace std;
ll ksm(ll a,ll b) {ll res=1;a%=mod; while(b){if(b&1)res=res*a%mod;b>>=1;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b) {return a/gcd(a,b)*b;}
ll inv(ll x) {return ksm(x, mod-2);}
int mp[N];
void solve(){
int n,k;
cin>>n>>k;
vector<int>v(n);
for(int i=1;i<=n;i++) {
cin >> v[i - 1];
mp[v[i-1]]=i;
}
int pos=1;
for(int i=2;i<=n;i++){
if(mp[i]>mp[i-1])
pos=i;
else break;
}
ll ans=(n-pos+k-1)/k;
cout<<ans<<'\n';
}
int main(){
int t=1;
cin>>t;
while(t--)
solve();
}
C. Elemental Decompress
题意:
给t组数据,每组数据给定一个n和长度为n的序列a,要求构造两个长度同样为n的排序p,q,使得
ai=max(pi,qi),如果能够构造出这样的序列,输出YES并输出两个序列,不能则输出NO。
题解:
首先判断构造不出的情况,很明显,某个元素出现3次及以上肯定构造不出,特别的,因为没有元素小于1,所以1最多出现一次。
再来构造p和q,贪心的思路,不妨把序列a中的元素尽量从p中拿(这对结果并没有影响),除非当前元素p中已经放过了,才放入q中,代码表现为:
for(int i=1;i<=n;i++)
if(!vis1[v[i]])a[i]=v[i],vis1[v[i]]=1;
else b[i]=v[i],vis2[v[i]]=1;
然后,我们可以知道对于还没有填元素的位置,要么p[i]是空的,要从q还没用过的元素中取,要么q[i]是空的,要从p还没用过的元素中取,namo,怎么取元素最合适呢?如p[i]=4时,q没用过的元素中有4和1,当然是选4而不是1,因为1比4能满足更多个p[i],我们该选择满足最低限度的元素,主打的就是一个贪心。
你可以通过线段树查询<=p[i]的最大元素填入,但是对于1300分的题目多少有点大材小用,开个pair数组记录p[i](q[i])和他们对应的位置,然后复制一份并且排序,然后把没有用过的元素存入优先队列,对每一个值选择满足最低限度的值(这个值就是优先队列的top),如果没有满足要求的值,那么说明构造不出这样的序列,退出即可。
本题代码:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<cstring>
#include<set>
#include<queue>
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const int N = 2E6 + 7;
using namespace std;
ll ksm(ll a,ll b) {ll res=1;a%=mod; while(b){if(b&1)res=res*a%mod;b>>=1;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b) {return a/gcd(a,b)*b;}
ll inv(ll x) {return ksm(x, mod-2);}
void solve(){
int n;cin>>n;
vector<int>cnt(n+1),vis1(n+1),vis2(n+1);
vector<int>v(n+1),a(n+1),b(n+1);
vector<pair<int,int>>pv1(1),pv2(1);
for(int i=1;i<=n;i++)
cin>>v[i],cnt[v[i]]++;
for(auto it:v)
if(cnt[it]>2||cnt[1]>1){
cout<<"NO"<<'\n';
return;
}
priority_queue<int,vector<int>,greater<>>q1,q2;
for(int i=1;i<=n;i++)
if(!vis1[v[i]])a[i]=v[i],vis1[v[i]]=1,pv1.push_back({a[i],i});
else b[i]=v[i],vis2[v[i]]=1,pv2.push_back({b[i],i});
for(int i=1;i<=n;i++) {
if (!vis1[i])q1.push(i);
if (!vis2[i])q2.push(i);
}
sort(pv1.begin(),pv1.end());
sort(pv2.begin(),pv2.end());
for(int i=1;i<pv1.size();i++){
if(pv1[i].first>=q2.top())
b[pv1[i].second]=q2.top(),q2.pop();
else{
cout<<"NO"<<'\n';
return;
}
}
for(int i=1;i<pv2.size();i++){
if(pv2[i].first>=q1.top())
a[pv2[i].second]=q1.top(),q1.pop();
else{
cout<<"NO"<<'\n';
return;
}
}
cout<<"YES"<<'\n';
for(int i=1;i<=n;i++)
cout<<a[i]<<' ';
cout<<'\n';
for(int i=1;i<=n;i++)
cout<<b[i]<<' ';
cout<<'\n';
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t=1;cin>>t;
while(t--)
solve();
}
D.Lucky Permutation
![](https://img-blog.csdnimg.cn/img_convert/7646ca0b3860b2aa2a25349758560b69.png)
题意:
给出一个排列,你可以任意选择两个元素交换,求至少交换多少次能让序列中有且仅有一组逆序对。
题解:
首先,如果序列中仅有一对逆序对,那么这对逆序对一定是相邻的,我们可以考虑将序列变为有序后选择一对相邻元素交换。
先引入一个概念:置换环:对于升序的排列,肯定存在v[i]=i,也就是下标(i)和数值(v[i])相等,如果一个排列中存在有数值不等于位置,即v[i]!=i,同理就有下标v[i]和数值v[v[i]]不相等。
通俗的说:如果3的位置上不放3放了8,那么8的位置上肯定不会放着8,放了其他数字。
我们不断的去找当前数值指向的下一个位置,最终一定会指向最开始的位置,形成一个环。
如{1,7,5,8,2,6,3,4}
可以被拆分成{1},{7,3,5,2},{8,4},{6}这样四个环。
代码表现就是:
int now=i,head=i;//now当前节点,v[now]是下一个节点
int len=1;//cnt为环上有几个元素(环的长度)
while(v[now]!=head){
len++;
now=v[now];
}
namo,对于一个环,易得它可以帮我减少一次交换次数(环形交换,最有一个元素自动归位)
仅仅是排成有序序列的话我们只要计算n-cnt即可(cnt为环的个数),但是我们要保证有一对逆序对,所以说在遍历一个环的时候,我们要记录是否有相邻元素逆序对出现,如果有的话,我们不用换成有序,
少换一次就有一对逆序对出现了,以下是代码实现
本题代码:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<cstring>
#include<set>
#include<queue>
#define eol "\n"
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const int N = 2E6 + 7;
using namespace std;
ll ksm(ll a,ll b) {ll res=1;a%=mod; while(b){if(b&1)res=res*a%mod;b>>=1;a=a*a%mod;}return res%mod;}
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b) {return a/gcd(a,b)*b;}
ll inv(ll x) {return ksm(x, mod-2);}
void solve(){
int cnt=0,res=-1,n;cin>>n;
vector<int>v(n+1);
vector<int>vis(n+1);
for(int i=1;i<=n;i++)
cin>>v[i];
for(int i=1;i<=n;i++){
cnt+=(!vis[i]);
if(!vis[i]){
int now=i,head=i;//now当前节点,v[now]是下一个节点
map<int,int>mp;
while(v[now]!=head){
vis[now]=mp[v[now]]=1;
if(mp[v[now]-1]||mp[v[now]+1])res=1;
now=v[now];
}
vis[now]=mp[v[now]]=1;
if(mp[v[now]-1]||mp[v[now]+1])res=1;
}
}
cout<<n-cnt-res<<eol;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t=1;cin>>t;
while(t--)
solve();
}
E. Partial Sorting
![](https://img-blog.csdnimg.cn/img_convert/813e3e2fc5ef8bf27ec5c83f43b99ccc.png)
题意:
给出n和模数M,求长度为3n的排列变成升序的最小操作次数,对于一个排列,你有两种操作,第一种,排序前2n个数,第二种,排序后2n个数,你要输出的是所有满足长度为3n的排序的操作次数之和
题解:
为了方便表述,排列中i=v[i]我就称其已配对
让一个序列有序,最多只需要两次操作:
0次的:本身就有序
令条件a为前n个数配对,条件b为后n个数配对
1次的:a,b满足并且只满足一个
令条件c为小于等于n的数值分布在前2n的位置 条件d为大于n的数值分布在后2n的位置
2次的:c,d满足并且只满足一个
3次的:c,d同时不满足
然后我们开始排列组合和容斥!
0次的:
只有一组
1次的:
前n配对:后2n个元素随意排列,后n配对,前2n个元素随意排列
减去重复统计的,即a,b条件都满足的,也就是前n后n都配对,中间n个随意排列
所以答案为种
2次的:
选取前n个元素,在2n个位置里排列也就是A(2n,n)
选取后n个元素,在后2n个位置里排列,同上是A(2n,n)
剩下2n个数随意排列是(2n)!
然后减去重复统计的,即同时满足c,d条件的:
枚举前n个元素在后n个位置里出现的个数i,那么后n个元素能放的位置只有2n-i个,剩下n个随意排列,推推式子吧大伙:
3次的:
看起来啥条件都不满足,没规律很一般很难搞,但是你前面都容斥这么久了还没反应过来?你其他的算出来了,剩下一种减一下不就出来了?
最后对整体进行容斥
只3次的=3次的-2次的
只2次的=2次的-1次的
只1次的= 1次的-0次的
加起来就是答案了
上代码:
本题代码:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<cstring>
#include<set>
#include<queue>
#define eol "\n"
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int N = 2E6 + 7;
using namespace std;
ll n,mod;
ll ksm(ll a,ll b) {ll res=1;a%=mod; while(b){if(b&1)res=res*a%mod;b>>=1;a=a*a%mod;}return res%mod;}
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b) {return a/gcd(a,b)*b;}
ll inv(ll x) {return ksm(x, mod-2);}
ll fac[5000005],ifac[5000005];
void jc(int n){
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
ifac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=0;i--)
ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
}
ll C(int n,int m){
if(n<m) return 0;
return (1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod)%mod;
}
void solve(){
cin >> n >> mod;
jc(3 * n+1);
vector<ll>num(4);
num[0] = 1;
num[1] = (2 * fac[2*n]%mod - fac[n]+mod)%mod;
num[2] =( 2 * fac[n]* C(2ll*n, n) % mod * fac[2*n] % mod)%mod;
ll temp = (fac[n] * fac[n] )% mod * fac[n] % mod;
for(int i = 0; i <= n; i++) {
num[2] -= C(n, i) * C(n, n - i) % mod * C(2 * n - i, n - i) % mod * temp % mod;
num[2] = ((num[2] % mod) + mod) % mod;
}
num[3]=fac[n*3]%mod;
for(int i=3;i>=1;i--)
((num[i]-=num[i-1])+=mod)%=mod;
ll ans=0;
for(int i=0;i<=3;i++)
ans=((ans+(num[i]*i%mod))%mod+mod)%mod;
cout<<ans<<eol;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t=1;//cin>>t;
while(t--)
solve();
}