CF1859 前四题题解

2023-08-13 18:17:37

前言

本来想着至少 A 四道的,但是 T4 因为我《卓越》的运用 stl 的能力挂掉了。不过还好,至少 rated 没掉(困死了困死了)。

A

题意:

把长度为 \(n\) 的数组 \(a\) 分成两个非空部分 \(b,c\) 使得 \(c\) 中任意数都不是 \(b\) 的因数,如果没有方案输出 -1,否则输出一组可行解。

思路:

因数肯定不能比原数大啊,所以直接让最大的几个跑 \(c\) 去就行了,注意 \(b,c\) 不能为空即可。

\(Code\)

int n,a[102];
int main(){
    for(int T=read();T--;){
        n=read();
        int maxx=0;
        for(int i=1;i<=n;i++){
            a[i]=read();
            maxx=max(a[i],maxx);
        }
        int o=0;
        for(int i=1;i<=n;i++){
            if(a[i]==maxx)o++;
        }
        if(o==n)puts("-1");
        else{
            printf("%d %d\n",n-o,o);
            for(int i=1;i<=n;i++){
                if(maxx!=a[i])printf("%d ",a[i]);
            }
            puts("");
            for(int i=1;i<=o;i++)printf("%d ",maxx);
            puts("");
        }
        
    }
}

B

观察到我们把每个数组的最小值集中到一个数组即可,然后答案就是其他数组次小值的和减去最小值,为了让这个值最大,我们把所有最小数丢进次小值最小的数组中即可,答案为 $$\sum_{i=1}^n secondmin_{j=1}ma_{i,j}-\min_{i=1}n secondmin_{j=1}^m a_{i,j}+\min_{i=1}^n \min_{j=1}^m a_{i,j}$$

\(Code\)

int n,a[25003];
int main(){
    for(int T=read();T--;){
        n=read();
        int minn=2e9,minse=2e9;
        ll sum=0;
        for(int i=1;i<=n;i++){
            int m=read(),fi=read(),se=2e9;
            for(int j=1;j<m;j++){
                int w=read();
                if(w<=fi)se=fi,fi=w;
                else if(w>fi&&w<se)se=w;
            }
            sum+=se;
            minn=min(minn,fi);
            a[i]=se;
        }
        for(int i=1;i<=n;i++){
            minse=min(minse,a[i]-minn);
        }
        printf("%lld\n",sum-minse);
    }

}

C

说实话不会证明,随手用 next_permutation() 打了个 \([1,10]\) 的表,然后就发现了规律:答案是从某个位置开始倒序排列的,如 \(10\) 的答案是 \(1,2,3,4,5,6,10,9,8,7\) 的序列。\(O(n^2)\) 枚举即可。

\(Code\)

int n,a[255];
int main(){
    for(int T=read();T--;){
        n=read();
        ll ans=0;
        for(int i=1;i<=n;i++){
            for(int w=1;w<=i;w++)a[w]=w;
            ll sum=0;int maxx=0;
            for(int j=i;j<=n;j++){
                a[j]=i-j+n;
            }
            for(int k=1;k<=n;k++)sum+=a[k]*k,maxx=max(maxx,a[k]*k);
            sum-=maxx;
            if(sum>ans)ans=sum;
        }
        printf("%lld\n",ans);
    }
}

后话

没想到这样就直接过了,正解是 \(O(n^3)\) 可证明的,我觉得是可以直接 \(O(n)\) 求解的,好像评论区有人发了,不过我觉得复杂度反正够了就没有往那个方面想。

D

不要被最近学的图论优化给迷惑了!看到线段传送就往线段树优化建图上想,其实就是一个简单的线段合并,不过正解好像用的是排序后放 multiset 里用扫描线离线更新答案,复杂度跟我的方法一样是 \(O((n+q)\log n)\)

注意到,我传送肯定是尽可能跑到 \(b_i\) 的位置,能跑到越后面的 \(b_i\) 就往后传送。不然我们假设这样做不是最优的,那么我们肯定可以通过某种方法从当前的 \(x_i\) 位置往后跳,跳到比 \(b_i\) 更后面但是 \(b_i\) 跳不到的地方,也就是有一个不包含 \(b_i\) 却包含 \(x_i\),然后此处的 \(b_j>b_i\) 的位置,显然不存在。

那既然知道了在一个区间内就一定会往后面的 \(b_i\) 跳,所以 \(a_i\)\(r_i\) 就没有任何用处,注意到如果 \(b_i\) 被另外一个 \(l_j,b_j\) 的线段包含,那么我们显然可以跳到 \(b_j\) 可以跳到的地方。(这个时候我想到了用并查集维护,发现好像完全没必要)

所以我们可以直接把有重叠的 \([l_i,b_i]\) 线段直接合并成一个大线段,线段内所有位置的答案都是线段的最右处的坐标即可。合并线段可以先把线段按 \(l\) 排序,这样不会出现后来的线段包含先来的线段的情况,减少一些特判,然后用 lower_bound,找第一个大于等于 \(l\)\(r\) 即可。查询时也是用 lower_bound 找第一个大于等于 \(x\)\(r\),然后判断是否在线段内。

\(Code\)

struct line{
    int l,r;
    inline bool operator <(const line &w)const{
        if(l==w.l)return r>w.r;
        return l<w.l;
    }
}a[200005];
map<int,int>S;
int n,q;
int main(){
    for(int T=read();T--;){
        n=read();
        for(int i=1;i<=n;i++){
            int l=read(),A=read(),b=read(),r=read();
            a[i]={l,r};
        }
        S.clear();
        sort(a+1,a+1+n);
        for(int i=1;i<=n;i++){
            auto loc=S.lower_bound(a[i].l);
            if(/*loc==S.begin()||*/i==1||loc==S.end()){
                S[a[i].r]=a[i].l;
            }else{
                if(a[i].r<=loc->first)continue;
                S[a[i].r]=loc->second;
                S.erase(loc->first);
            }
        }
        q=read();
        while(q--){
            int x=read();
            auto loc=S.lower_bound(x);
            if(loc==S.end()||loc->second>x)printf("%d ",x);
            else printf("%d ",loc->first);
        }
        puts("");
    }
}

后话

是的,这么简单的代码我在考场上打挂了,因为我以为 <map>.begin() 是开始位置的前一个.....

题解做法

我感觉题解那个做法蛮有意思的,所以想着自己打一下。

基本思想和我的差不多,都是只考虑 \(l,b\),然后对可以被包含的答案进行更新。

这里具体是将每个 \(l,b,x\) 按照 \(b>x>l\) 的优先级放进数组中,然后用 multiset 维护答案。从坐标从大到小每扫到一个 \(r\) 就更新当前位置的答案,扫到 \(x\) 就加入答案,扫到 \(l\) 就把上一个位置的答案删除。

\(Code\)

struct line{
    int x,type,id;
    inline bool operator <(const line &w)const{
        if(x==w.x)return type>w.type;//优先级设置
        return x>w.x;
    }
};
vector<line>a;
multiset<int>S;
int n,q,ans[200005],Q[200005];
int main(){
    for(int T=read();T--;){
        n=read();
        S.clear();
        a.clear();
        for(int i=1;i<=n;i++){
            int l=read(),A=read(),b=read(),r=read();
            ans[i]=r;//答案先为自己
            a.push_back({l,-1,i});
            a.push_back({r,1,i});
        }
        q=read();
        for(int i=1;i<=q;i++){
            int x=read();
            Q[i]=x;
            a.push_back({x,0,i});
        }
        sort(a.begin(),a.end());
        for(auto [x,tp,i]:a){
            if(tp==1){
                if(!S.empty())ans[i]=*S.rbegin();//后面答案肯定大于当前答案,更新
                S.insert(ans[i]);
            }else if(tp==0){
                if(!S.empty())Q[i]=max(Q[i],*S.rbegin());//答案为最末答案
            }else{
                S.extract(ans[i]);//用 extract 是释放一个值,而用 erase 会给你全删了。
            }
        }
        for(int i=1;i<=q;i++){
            printf("%d ",Q[i]);
        }
        puts("");
    }

}

后记

写的速度还是太慢了,正确率也不够高,要经常对着样例调代码,最后两题也还是等我之后有时间了再订正吧。

posted @ 2023-09-08 10:41  NBest  阅读(24)  评论(0)    收藏  举报