Codeforces Round #842 (Div. 2) 个人题解

A. Greatest Convex

题意:

给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

题意:

给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

题意:

给出一个排列,你可以任意选择两个元素交换,求至少交换多少次能让序列中有且仅有一组逆序对。

题解:

首先,如果序列中仅有一对逆序对,那么这对逆序对一定是相邻的,我们可以考虑将序列变为有序后选择一对相邻元素交换。

先引入一个概念:置换环:对于升序的排列,肯定存在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

题意:

给出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();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值