Codeforces Round #789 (Div. 2) A~F个人题解

Dashboard - Codeforces Round #789 (Div. 2) - Codeforces

A. Tokitsukaze and All Zero Sequence

题意:有一个长度为n的数组a,每次可以从数组种选择俩个下标不同的元素进行下面俩个操作:

(1)如果两个元素相同,把其中一个变成0,(if a_i=a_j,update \ a_i=0\ or\ a_j=0)

(2)如果两个元素不同,把两个元素都变成这两个的最小值(if a_i\neq a_j,update\ a_i=a_j=min(a_i,a_j))

问,最少的操作数把数组a全部变成0?

知识点:思维

思路:可以分类一下,

1、如果数组中本来就有0,那操作数一定是不为0的数和0进行(2)操作,答案就是不为0的个数

2、如果数组中没有0,但是有相同的两个元素,那进行一次(1)操作,把一个不为0的数变成0,剩下的又是情况1、,答案就是 1+(不为0的个数-1)

3、数组中没有0,没有相同的两个元素,进行一次操作(2),变出两个想等的元素,剩下的又是情况2、,答案就是  1+[1+(不为0的个数-1)]

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e6+5;
const ll mod =998244353;
ll a[N];
void solve(){
    ll n;cin>>n;
    for(int i=1;i<=n;++i)cin>>a[i];
    ll res=0;//不为0的元素的个数
    for(int i=1;i<=n;++i)if(a[i])res++;
    sort(a+1,a+n+1);//排个序,让相等元素相邻
    if(a[1]==0)cout<<res<<'\n';//有0,情况1、
    else {
        for(int i=2;i<=n;++i){
            if(a[i]==a[i-1]){//有相等元素,情况2、
                cout<<res<<'\n';
                return ;
            }
        }
        cout<<res+1<<'\n';//都没有,情况3、
    }
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

B1&2. Tokitsukaze and Good 01-String 

题意:有一个长度为偶数的01串,

定义数组d,表示这个01串连续子串的长度,比如 01串“100101001”,数组d={1,2,1,1,1,2,1}("1","00","1","0","1","00","1")。

定义一个01串是好的,那要满足这个01串的数组d的元素都是偶数。

你可以进行任意次操作,每次操作可以让01串的0变成1,1变成0

问最少的操作数把这个01串变成好的,并且在此前提下让数组d的长度最小?

输出 最少操作数和数组d的长度

知识点:贪心,思维

思路:首先要注意到,01串长度是偶数,连续子串的长度也要是偶数,那么01串S的S[i]和S[i+1]一定是相同的(S从下标1开始,i是奇数)。

那么对于最少的操作数,每次看S[i]和S[i+1],如果相同就不用改,如果不同改其一,答案就出来了。

然后是让数组d长度最小,每次看S[i]和S[i+1],如果相同是不用改的,如果不同改其一,改哪个?

这下就是贪心了,如果他前面的字符是0,那我就改成00,如果是1,改成11。

都改完了,相同的合并,最后的长度就是答案。

(有一个点,如果开始的两个就不同怎么办,我们可以开一个-1的状态,表示他是谁都可以)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 2e5+5;
const ll mod =998244353;
int s[N];
void solve(){
    ll n;cin>>n;
    for(int i=1;i<=n;++i){
        char c;
        cin>>c;
        s[i]=c-'0';//输入01串
    }
    int pre=-1;//记录他前面是谁,-1表示任意
    ll ans=0,d=0;//操作数,长度
    for(int i=1;i<=n;i+=2){
        if(s[i]^s[i+1]==0){//如果这两个相等
            if(s[i]==pre)continue;//如果等于他前面的,那继续
            d++;//不等于他前面的,长度+1
            pre=s[i];//更新
        }
        else ans++;//如果两个不等,操作数要+1
    }
    d=max(1ll,d);//上面代码可能d++操作都不会执行,但是长度一定是大于等于1的,所以取个1
    cout<<ans<<' '<<d<<'\n';
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

C. Tokitsukaze and Strange Inequality

题意:给你一个长度为n的数组p,问有多少个本质不同的四元组(a,b,c,d)满足1\leq a<b<c<d \leq n \ and \ p_a<p_c\ and \ p_b>p_d

知识点:计数,桶,前缀和

思路:我是通过枚举A,D来实现的,每次把一个A放入桶中,选择一个B,然后往后看C的时候,看桶中比C小的A的个数,存到一个变量res里,如果看D的时候满足D<B,答案就+res(这个A,B,C,D一定满足下标的关系)

首先第一步确定了 A<C的对数,当再确定D<B的时候,这个四元组一定是满足的,因为是枚举的,所以肯定是全部都看过的,因为A,B不同,所以也不会出现重复。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 5e3+5;
const ll mod =998244353;
ll a[N],has[N],d[N],n;//d是桶,has是前缀和数组
void solve(){
    cin>>n;
    for(int i=1;i<=n;++i)cin>>a[i],d[i]=0;
    ll ans=0;
    for(int i=3;i<n;++i){//枚举A
        d[a[i-2]]++;//桶中元素A的个数+1
        for(int j=1;j<=n;++j)has[j]=has[j-1]+d[j];//前缀和
        ll res=has[a[i]];//开始看A,C关系
        for(int j=i+1;j<=n;++j){//枚举D
            if(a[i-1]>a[j])ans+=res;//如果B>D
            res+=has[a[j]];//更新A,C关系
        }
    }
    cout<<ans<<'\n';
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

D. Tokitsukaze and Meeting

题意:现在有一个n\times m大小的会议室,有n\times m个学生,每个学生都有两个属性0和1(?),他们依次进入会议室,依次进入规则如下图

 问,对于每个时刻,有多少行,多少列的和不为0?

输出每个时刻,满足条件的行和列的个数和

知识点:思维

思路:可以考虑列和行分开来算,

我们定义ans[i]为第i个时刻满足条件的列的个数

对于列来说,每次一个属性为0的学生进入会议,会发现他们每次都符合

其实也很好想,每次来一个列下标都进1再取模就这样了,

这时ans[i]=ans[i-1],因为0不会把这一列改成符合的

如果是属性为1的学生进入会议 

 会发现,1可以把这一列改成符合的,所以我们要看这个绿色的是不是满足了,因为都是同一列的,他们对行长取模大小相同,所以我们维护一个对行长取模的数组来看这一列是不是满足了就行了。

我们定义res[i]为第i个时刻满足条件的行的个数

对于行来说,每次学生进入会议,会发现他们每次都符合

 这个绿色框子里行的个数其实我们早在之前算过了,所以问题就是第一行是不是满足,所以维护一个长度为行长的第一行就行了,看是不是符合,res[i]=res[i-m]+(符合),不够一行就没有res[i-m]所以只看符合不符合就行了,res[i]=(符合)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e6+5;
const ll mod =998244353;
ll ans[N],res[N];//列个数,行个数
bool has[N];
ll n,m;
ll getid(ll x){//因为我是下标从1开始,所以取模要改一下
    if(x%m==0)return m;
    return x%m;
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n*m;++i)ans[i]=res[i]=0,has[i]=false;//初始化
    string s;cin>>s;
    s='#'+s;//让下标从1开始
    for(int i=1;i<s.length();++i){
        if(s[i]=='1'){
            if(i<=m)ans[i]=ans[i-1]+1;//还不够一行
            else {//够一行了
                if(has[getid(i)])ans[i]=ans[i-1];//如果这一列已经是满足的了
                else ans[i]=ans[i-1]+1;//如果这一列是加上这个1才满足的
            }
            has[getid(i)]=true;//这一列已经满足了
        }
        else ans[i]=ans[i-1];
    }
    ll d=0;//第一行1的个数
    for(int i=1;i<s.length();++i){
        if(i<=m){//不够一行
            if(s[i]=='1')d++;
            res[i]=(d>=1);//这一行个数不为0
        }
        else {
            if(s[i-m]=='1')d--;//滑动,更新
            if(s[i]=='1')d++;//滑动,更新
            res[i]=(d>=1)+res[i-m];//这一行满足不满足加下面那个已经算过的
        }
    }
    for(int i=1;i<=n*m;++i)cout<<ans[i]+res[i]<<' ';//输出每个时刻行个数和列个数的和
    cout<<'\n';
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

E. Tokitsukaze and Two Colorful Tapes

题意:给你俩个长度为n的排列p_1,p_2,现在需要你来构造一个长度为n的排列w,

满足\sum_{i=1}^{n}|w[p_{1,i}]-w[p_{2,i}]|最大?(p_{1,i}表示排列p_1的第i个值,p_2同理)

知识点:贪心,并查集

思路:可以发现这两个排列组成多个轮换,也就是环。

我们先考虑单个环,

如果环长是偶数,我们设a_i为环上某点的价值,并且设(a_{2i}>a_{2i+1},1\leq i\leq \frac{n}{2}),那么这个环的价值就是2(\sum_{i=1}^{\frac{n}{2}}a_{2i}-\sum_{i=1}^{\frac{n}{2}}a_{2i-1}),怎么最大?

肯定贪心的想,a_{2i}取从n往下的\frac{n}{2}个,a_{2i+1}取从1往上的\frac{n}{2}个。

如果环长是奇数,我们设a_i为环上某点的价值,并且设(a_{2i}>a_{2i+1},1\leq i\leq \frac{n-1}{2}),那么这个环的价值就是2(\sum_{i=1}^{\frac{n-1}{2}}a_{2i}-\sum_{i=1}^{\frac{n-1}{2}}a_{2i-1}),怎么最大?

跟上面是偶数的情况是一样的取法。

然后你就会发现,其实奇数偶数的情况下想取到最大的方法是相同的,都是一个从大往小取\left \lfloor \frac{n}{2} \right \rfloor个,一个从小往大取\left \lfloor \frac{n}{2} \right \rfloor个。

现在开始考虑多个环,设环长用d_i表示,

我们对每个环单独考虑,这样最后就是从大往小取了\left \lfloor \frac{\sum_{i=1}d_i}{2} \right \rfloor个,从小往大取了\left \lfloor \frac{\sum_{i=1}d_i}{2} \right \rfloor

然后答案加加减减就出来了。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =998244353;
struct dsu{
    const int n;
    vector<ll>f;
    inline dsu(int n) : n(n), f(n+1){
        for(int i=1;i<=n;++i)f[i]=i;
    }
    inline ll Find(ll x){return x==f[x]?x:(f[x]=Find(f[x]));}
    inline void Merge(ll u,ll v){
        ll tx=Find(u),ty=Find(v);
        if(tx!=ty)f[ty]=tx;
    }
};
ll a[N],b[N],d[N];
void solve(){
    ll n;cin>>n;
    dsu w(n);
    for(int i=1;i<=n;++i)d[i]=0;//环长初始化
    for(int i=1;i<=n;++i)cin>>a[i];
    for(int i=1;i<=n;++i)cin>>b[i];
    for(int i=1;i<=n;++i){
        w.Merge(a[i],b[i]);//开始并查集合并
    }
    for(int i=1;i<=n;++i){
        ll l=w.Find(w.f[i]);//属于同一个集合,相当于属于同一个环里
        d[l]++;//环长+1
    }
    ll limit=0,ans=0;
    for(int i=1;i<=n;++i){
        limit=limit+(d[i]>>1);//计算从上往下取多少个
    }
    for(int i=1;i<=limit;++i){//计算贡献,其实可以O(1)算
        ans=ans+n-2*i+1;//i 和 n-i+1
    }
    cout<<2*ans<<'\n';//最后记得乘上开始那个2
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

F. Tokitsukaze and Permutations

题意:对于一个长度为n的排列,我们可以执行操作,

每次操作,对于每一个i从1到n-1,如果p_i>p_{i+1},交换这两个数。

定义数组v,v_i表示从1到i-1的比p_i大的数的个数

现在给定数组v,问你有多少个长度为n的排列执行k次操作后的v数组和给定的一样?(给定的v数组会出现-1的情况,表示这个位置的值不知道)

知识点:数学

思路:可以发现这个操作类似于冒泡排序的一步,而这一步相当于把v数组所有的v_i变成max(0,v_i-1),然后向左移动一位,最后一位补0,

其实也很好理解,每次选一个大的数往后移动,这些v的值肯定要减1.

然后我们要知道对于一个数组v(不出现-1),他是和某一个排列是一一对应的

证明:我们从后往前看v_i的大小,他一定是这个排列中第v_i+1大的数,然后把这个数从排列里去掉,继续看v_{i-1}.............

所以答案相当于问有多少个v数组,

首先,操作执行k次后,我们最后k个数字一定是最大的k个数,他们的v_i只能是0或者-1,不然我们是没有数组v变成这样的

然后,对于原来的数组v,我们的前k位其实在最后是看不出是谁的,所以前k位只要是符合的都是可以的,相当于一个长度为k的数组有多少个不同的排列,所以ans先等于k!,

对于k+1~n来说,我们要看v_{i-k}的值,

如果大于0,相当于这位是固定的ans=ans*1,

如果等于0,那这位可以是从0~k的其中任意一个数字变来的,所以ans=ans*(k+1)

如果等于-1,那这位可以是从0~i-k-1+k的其中任意一个数字变来的,所以ans=ans*i

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e6+5;
const ll mod =998244353;
ll v[N];
void solve(){
    ll n,k;cin>>n>>k;
    for(int i=1;i<=n;++i)cin>>v[i];
    for(int i=n-k+1;i<=n;++i){
        if(v[i]>0){//如果后k位,不是0或者-1,那一定没有数组能变过来
            cout<<0<<'\n';
            return ;
        }
    }
    ll ans=1;
    for(ll i=1;i<=k;++i)ans=ans*i%mod;//ans=k!
    for(ll i=k+1;i<=n;++i){
        if(v[i-k]==-1){//i种选择
            ans=ans*i%mod;
        }
        else if(v[i-k]==0){//(k+1)种选择
            ans=ans*(k+1)%mod;
        }
    }
    cout<<ans<<'\n';
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

最近考试耽误了一点时间,补的有点晚了

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值