2021牛客暑期多校训练营4

F Just a Joke

题意

博弈游戏,给一个无环图,每次可以删除一条边或者无环一个连通分量。

解析

由于删除的是无环连通分量,因此 k k k个点的连通分量必须是 k − 1 k-1 k1条边,当我们删除点的时候减少的边数和点数为 2 k − 1 2k-1 2k1

我们有两种操作,都是减少奇数个元素。

  1. 减少一条边
  2. 减少一个连通分量

我们最开始的元素总数为 ( n + m ) = k (n+m)=k (n+m)=k,假设A先手,使得K减去一个奇数,变为一个偶数,随后B出手,使得这个偶数又变为奇数。因此每一轮次操作后,结果的奇偶性不变,而操作后元素为0才算胜利,因此只要判断元素总和的奇偶性即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
    int n,m;
    cin>>n>>m;
    if((n+m)&1)cout<<"Alice";
    else cout<<"Bob";
}

感悟

场上写的时候没有发现是无环连通分量,但还是侥幸过了。注意博弈每一步操作的本质。

i Inverse Pair

题意

对序列的元素+1或者+0,构造出新的序列,求经过这个操作后,此序列的最小逆序对。

题解一(二分+归并排序

我们对元素+1能消除哪种类型的逆序对呢?

( a i , a j ) ( i < j , a i = a j + 1 ) (a_i,a_j)(i<j,a_i=a_j+1) (ai,aj)(i<j,ai=aj+1)

所以我们只需要在归并求逆序对的时候,利用二分查找找到上述 a i a_i ai的个数,然后通过+1的操作,让这些逆序对消除。最后结果就是原数组的逆序对减去被消除的逆序对的个数。

但是,如果我们对 a i a_i ai进行加1的操作去消除逆序对,那么我们的 a i + 1 a_i+1 ai+1就不能再进行加一了,本题解中称为固定 a i + 1 a_i+1 ai+1,被固定的值就不能带来任何收益,我们将其收益标记为0。因此我们要进行一个取舍,选择收益最大的。

假设 4 4 3 2 ,最优的策略是对3进行+1,而不是对2进行+1,因此我们只需要判断+1后能带来的收益哪个更大,被固定的值就不能带来任何收益,因此将其收益标记为0。

时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200000+5;
int a[N];
int temp[N];
long long int ans=0;
map<int,ll>mp;
void merge_sort(int a[],int l,int r){
    if(l>=r)return;
    int mid=l+r>>1;
    merge_sort(a,l,mid);
    merge_sort(a,mid+1,r);
    int idx=l;
    int i=l,j=mid+1;
    while(i<=mid && j<=r){
        if(a[i]>a[j]){
            ans+=mid-i+1;
            // i~mid [i,mid]找到大于1的个数
            int index=upper_bound(a+i,a+mid+1 ,a[j]+1)-a;
            int value=index-i;
            mp[a[j]]+=(value);
            temp[idx++]=a[j++];
        }
        else{
            temp[idx++]=a[i++];
        }
    }
    while(i<=mid){
        temp[idx++]=a[i++];
    }
    while(j<=r){
        temp[idx++]=a[j++];
    }
    for(int k=l;k<=r;k++)
        a[k]=temp[k];
}
int main(){
    int n ;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    merge_sort(a,1,n);
    ll res=0;
    for(auto i:mp){
        if(mp.find(i.first+1)!=mp.end() ){
            if(i.second >= mp[i.first+1]){ 
                mp[i.first+1]=0;//固定
                res+=i.second;
            }
        }
        else{//不需要进行取舍,直接消除。
            res+=i.second;
        }
    }
    cout<<ans-res<<endl;
}

题解二(建图

视频讲解的做法。建树状数组


C LCS

题意

构造题,给出 L C S ( s 1 , s 2 ) = a , L C S ( s 2 , s 3 ) = b , L C S ( s 1 , s 3 ) = c LCS(s1,s2)=a,LCS(s2,s3)=b,LCS(s1,s3)=c LCS(s1,s2)=a,LCS(s2,s3)=b,LCS(s1,s3)=c

讨论一下No的情况

先求出LCS最小值 m i mi mi作为公共部分,每一个LCS都减去 m i mi mi,最长重合部分为 a + b + c + m i < = n a+b+c+mi<=n a+b+c+mi<=n(a,b,c其中一个已经为0),如果此条件不成立,就无解。

解析

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    int a,b,c,n;
    cin>>a>>b>>c>>n;
    int mi=min({a,b,c});//公共部分
    a-=mi,b-=mi,c-=mi;
    if(a+b+c+mi>n){
        cout<<"NO";
    }
    else{
        string aa(mi,'a'),bb(mi,'a'),cc(mi,'a');
        for(int i=1;i<=a;i++){
            aa+='b';
            bb+='b';
        }
        for(int i=1;i<=b;i++){
            bb+='c';
            cc+='c';
        }
        for(int i=1;i<=c;i++){
            aa+='d';
            cc+='d';
        }
        while(aa.length()<n)aa+='x';
        while(bb.length()<n)bb+='y';
        while(cc.length()<n)cc+='z';
        cout<<aa<<endl<<bb<<endl<<cc<<endl;
    }
}

J Average

题意

两个数组 a [ i ] , b [ j ] a[i],b[j] a[i],b[j]​构建矩阵 w [ i ] [ j ] w[i][j] w[i][j]​,求此矩阵的平均值最大的子矩阵

解析

w [ r 1   r 2 ] [ l 1   l 2 ] w[r_1~r_2][l_1~l_2] w[r1 r2][l1 l2]​​的平均值公式为
∑ i = r 1 r 2 ∑ j = l 1 l 2 w [ i ] [ j ] ( r 2 − r 1 + 1 ) ∗ ( l 2 − l 1 + 1 ) \frac{\sum_{i=r_1}^{r_2}\sum_{j=l_1}^{l_2} w[i][j]}{(r_2-r_1+1)*(l_2-l_1+1)} (r2r1+1)(l2l1+1)i=r1r2j=l1l2w[i][j]
由于 w [ i ] [ j ] = a [ i ] + b [ j ] w[i][j]=a[i]+b[j] w[i][j]=a[i]+b[j],所以我们转换一下。
∑ i = r 1 r 2 ∑ j = l 1 l 2 ( a [ i ] + b [ j ] ) ( r 2 − r 1 + 1 ) ∗ ( l 2 − l 1 + 1 ) \frac{\sum_{i=r_1}^{r_2}\sum_{j=l_1}^{l_2}( a[i]+b[j])}{(r_2-r_1+1)*(l_2-l_1+1)} (r2r1+1)(l2l1+1)i=r1r2j=l1l2(a[i]+b[j])
再转换一下
( l 2 − l 1 + 1 ) ∑ i = r 1 r 2 a [ i ] + ( r 2 − r 1 + 1 ) ∑ j = l 1 l 2 b [ j ] ( r 2 − r 1 + 1 ) ∗ ( l 2 − l 1 + 1 ) \frac{(l_2-l_1+1)\sum_{i=r_1}^{r_2} a[i]+(r_2-r_1+1)\sum_{j=l_1}^{l_2}b[j]}{(r_2-r_1+1)*(l_2-l_1+1)} (r2r1+1)(l2l1+1)(l2l1+1)i=r1r2a[i]+(r2r1+1)j=l1l2b[j]
化简得
∑ i = r 1 r 2 a [ i ] ( r 2 − r 1 + 1 ) + ∑ j = l 1 l 2 b [ j ] ( l 2 − l 1 + 1 ) \frac{\sum_{i=r_1}^{r_2} a[i]}{(r_2-r_1+1)}+\frac{\sum_{j=l_1}^{l_2} b[j]}{(l_2-l_1+1)} (r2r1+1)i=r1r2a[i]+(l2l1+1)j=l1l2b[j]
我们观察式子可知,答案为两个序列的子序列最大平均值,因此我们二分,分别求出两个序列的最大平均值,然后相加即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
double eps=1e-7;
const int N=1e5+5;
int a[N],b[N];
double pa[N];
int n,m,x,y;
bool check(int *a,int n,double avg,int x){
    for(int i=1;i<=n;i++){
        pa[i]=pa[i-1]+a[i]-avg;
    }
    double mi=0,mx=INT_MIN;
    for(int i=x;i<=n;i++){
        int j=i-x;
        mi=min(mi,pa[j]);
        mx=max(mx,pa[i]-mi);
    }
    return mx>=0;
}

double get_ans(int *a,int n,int x){
    double l=0,r=1e7;
    while(r-l>eps){
        double mid=(l+r)/2;
        check(a,n,mid,x)?l=mid:r=mid;
    }
    return l;
}

int main(){
    cin>>n>>m>>x>>y;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++)cin>>b[i];
    double ans=get_ans(a,n,x)+get_ans(b,m,y);
    printf("%.10lf",ans);
}

心得

遇到这种相加求平均的题,当时一看就是二分+前缀和,但是还是没写出来,因为公式没化简好,所以遇到这种题目先撸几发公式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值