CF 2224 Div.2 总结

终究还是太菜了 /咦

我怎么只做了三道题 /dk /dk /dk

T4 赛后一眼秒了,我赛时在干什么,难猜死了。


蛙趣,突然意识到能在洛谷上写 tj,那我得好好写总结了。水四篇题解谁不要呢


A. Zhily and Array Operating

简单题。

由于作用在 \(a_i\) 上的操作的效果是取决于 \(a_{i+1}\) 的,所以我们可以倒着来,这样走到 \(a_i\) 的时候 \(a_{i+1}\) 可能就更优了,而如果你正着来的话 \(a_{i+1}\) 都还没变化呢,显然不是最优解。

那么什么情况下做操作呢,很显然,只要 \(a_{i+1} > 0\) 就一定要做,因为这样能使 \(a_i\) 变得更大,成为(或帮助其他位置成为)正数的可能性就更大;否则一定不要做。\(a_{i+1} = 0\) 的话可做可不做。

全部操作完之后再统计一下有多少个正数即可,不难证明这样一定是最优的。

赛时代码:

#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
LL T,n,a[N],Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
    T=read();
    while(T--){
        n=read();Ans=0;
        for(int i=1;i<=n;i++)a[i]=read();
        for(int i=n-1;i>=1;i--)
            if(a[i+1]>=0)a[i]+=a[i+1];
        for(int i=1;i<=n;i++)if(a[i]>0)Ans++;
        cout<<Ans<<"\n";
    }
    return 0;
}

B. Zhily and Mex and Max

啊啊啊这个贪心我不会证啊!有无大手子证一下 /kel 我只是感觉很优就写了然后就 A 了我也不会证啊啊啊啊啊啊!

太菜了导致的,嗯对。

这个 \(\max\) 是前缀 \(\max\),那么我们可以直接把最大的 \(\max a\) 放到 \(a_1\) 的位置上,这样不论哪个前缀的 \(\max\) 都是最大值啦!显然是非常优的好吧。

此时第一位的 \(\text{mex}\) 就极有可能为 \(0\) 了(\(\max a = 0\) 这类情况可以特判,后面会提到),接下来要做的就是尽可能最快速最大化 \(\text{mex}\)。

整个序列能取到的 \(\text{mex}\) 即最大 \(\text{mex}\) 是好求的(升序排序做完了),那么接下来要做的就是将排序后的 \(a_i\) 逐位填入后序构造的序列中啦!……吗?

错!前面我特意加粗了,我们需要最快速最大化 \(\text{mex}\)。最快速!最快速!最快速!重要的事情说三遍但是你直接这样填是最快的吗?那肯定不一定咯,如果对于一个 \(i\) 有 \(a_i = a_{i-1}\),那么填了这个 \(a_i\) 根本不会提高当前前缀的 \(\text{mex}\) 值啊,那填它做什么,扔一边去,把 \(\text{mex}\) 提升到最大后再当做垃圾值随意填在末尾即可。换句话说,前面填数以把 \(\text{mex}\) 逼到极值时要先去重。

然后你每一位枚举算一下或者一个等差数列求和公式干一下就行了。

最后需要判断的一个地方就是当最大的 \(\text{mex}\) 等于 \(\max a + 1\) 时,填 \(\max a - 1\) 时的 \(\text{mex}\) 就不是 \(\max a\) 而是 \(\max a + 1\)(因为最开始放了一个 \(\max a\) 了),于是我们需要特判这个情况并对应改变答案。

具体的一些细节看代码吧。

#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
LL T,n,a[N],Max,MEX,Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
    T=read();
    while(T--){
        n=read();
        for(int i=1;i<=n;i++)a[i]=read();
        sort(a+1,a+n+1);
        Max=a[n],MEX=0;
        for(int i=1;i<=n;i++)
            if(a[i]==MEX)MEX++;
        Ans=Max*n+(MEX+1)*MEX/2;
        Ans+=(n-MEX-1)*MEX;
        if(Max<MEX)Ans=Ans-Max+MEX;
        cout<<Ans<<"\n";
    }
    return 0;
}

C. Zhily and Bracket Swapping

唔……诈骗。我也不知道我为什么卡了一个小时 /lb 终究还是太菜了 /ll

判断一个括号序列是否合法,其实不必用什么栈,我们把括号序列中的左括号 ( 看作 \(+1\)、右括号 ) 看作 \(-1\),那么这个括号序列合法等价于所有前缀之和都非负且全部的和为 \(0\)。可以自己试着证一下为什么,这个还是很简单的,我就不展开讲了 /kel。

于是现在问题就转化为给定两个由 \(+1\) 和 \(-1\) 组成的长度为 \(n\) 的数组 \(a\) 和 \(b\),且所有前缀之和都要非负,且最终的和为 \(0\)。

有一个特殊情况可以先判掉:如果 \(\sum a + \sum b \not= 0\),那这两串东西就没救了,就算完全打乱重排也不可能同时合法,直接输出 NO 就行。好吧骗你的,其实不判也行,也会在最后被否掉,但我喜欢先把特殊情况判掉喵。qwq

哦,然后呢?然后我们就要去枚举这个前缀了。枚举 \(i\) 从 \(1\) 到 \(n\),令 \(A\) 和 \(B\) 是当前实时的两个前缀和。为了方便描述,令 \(A \ge B\)。我们肯定希望 \(A\) 和 \(B\) 的值尽可能均衡——不要大的太大、小的太小,否则不仅容易引发负前缀和情况,最终和也很难为 \(0\)。因此在调整的时候,我们让 \(A \gets A + \min(a_i,b_i)\) 且 \(B \gets B + \max(a_i,b_i)\) 即可。任何时刻如果有 \(A<0\) 或 \(B<0\) 都可以直接 break 掉循环输出 NO。

最后还要判断一下最终是否 \(A=0\) 且 \(B=0\),都为 \(0\) 才 YES,否则都是 NO。

其实如果最开始如果你特判了全部的和为 \(0\) 那么循环走到最后还没有 break 其实只剩 YES 了,证明留给读者。

#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
int T,n,a[N],b[N],mx,mn;
bool NO;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
    T=read();
    while(T--){
        n=read();
        for(int i=1;i<=n;i++){
            char c;cin>>c;
            a[i]=(c=='('?1:-1);
        }
        for(int i=1;i<=n;i++){
            char c;cin>>c;
            b[i]=(c=='('?1:-1);
        }
        mx=0,mn=0;NO=0;
        for(int i=1;i<=n;i++){
            if(mx<mn)swap(mx,mn);
            mx+=min(a[i],b[i]);
            mn+=max(a[i],b[i]);
            if(mx<0||mn<0){NO=1;break;}
        }
        if(!NO&&mx==0&&mn==0)cout<<"YES\n";
        else cout<<"NO\n";
    }
    return 0;
}

D. Zhily and Barknights

呜哇,赛时没想出来赛后一眼秒了,我赛时和赛后是不用的是两个不同的脑子 /yun

诶诶诶等下,我好像没脑子来着。

关键在于没有直接把全部的当做计数最后再除总数,输输输。

我们考虑分开统计,对于一个 \(c_i\),定义其能对逆序对数量带来的贡献为满足 \(j > i\) 但 \(c_j < c_i\) 的 \(j\) 个数(之和)。

那不就等价于,枚举 \(a_i \times b_j\),找到所有 \(k > i\) 且 \(l \not= j\) 的满足 \(a_k \times b_l < a_i \times b_j\) 的个数嘛!

如果不考虑 \(l \not= j\) 这条限制,我们就可以直接离散化后树状数组做完了。

但是加上了这个限制怎么办呢?可以先忽略限制算出一个答案,最后再把那些 \(l = j\) 的情况,即 \(a_k < a_i\) 时所带来的贡献减掉不就行了嘛。

注意,这里算出来的是所有情况下的逆序对数量之和,最后逆元变成期望即可。

然后就做完啦,时间复杂度是 \(O(n^2 \log{n^2})\) 的,瓶颈在于离散化和树状数组喵。

#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2005;
const int N2 = N*N;
const LL MOD = 998244353;
LL T,n,a[N],b[N];
LL p[N2],Ls[N2],cnt;
LL c[N2],Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void upd(LL x){while(x<=cnt)c[x]++,x+=x&-x;return;}
LL ask(LL x){LL sum=0;while(x)sum+=c[x],x-=x&-x;return sum;}
LL QP(LL x,LL y=MOD-2){
    LL as=1;
    while(y){
        if(y&1)as=as*x%MOD;
        x=x*x%MOD,y>>=1;
    }return as;
}
int main(){
    T=read();
    while(T--){
        n=read();
        for(int i=1;i<=n;i++)a[i]=read();
        for(int i=1;i<=n;i++)b[i]=read();
        cnt=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                p[++cnt]=a[i]*b[j],Ls[cnt]=p[cnt];
        sort(Ls+1,Ls+cnt+1);
        cnt=unique(Ls+1,Ls+cnt+1)-Ls-1;
        for(int i=1;i<=n*n;i++)
            p[i]=lower_bound(Ls+1,Ls+cnt+1,p[i])-Ls;
        Ans=0;
        for(int i=0;i<=cnt;i++)c[i]=0;
        for(int i=1,l=n*(n-1)+1,r=n*n;i<=n;i++,l-=n,r-=n){
            for(int x=l;x<=r;x++)
                Ans=(Ans+ask(p[x]-1))%MOD;
            for(int x=l;x<=r;x++)upd(p[x]);
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<i;j++)
                if(a[j]>a[i])Ans=(Ans+MOD-n)%MOD;
        Ans=Ans*QP(n*(n-1))%MOD;
        cout<<Ans<<"\n";
    }
    return 0;
}

E. Zhily and Signpost

会不了一点,那咋办。

题解出来之后再说,看情况补不补。

据说是同余加上 exCRT 再加上树剖这类东西做的,但是感觉好难啊。

F. Zhily and Cycle

全球只有一百个人做出来了,请输入文本。

那我还是算了喵。

posted @ 2026-05-06 19:53  嘎嘎喵  阅读(169)  评论(0)    收藏  举报