Codeforces Round #773 (Div. 2)

本文回顾了一场VPVPVP比赛中的四道题目,包括三角形边长判断、数字分组优化、序列操作限制和匿名状态查询。通过实例解析展示了HardWay、PowerWalking、GreatSequence和RepetitionsDecoding的解题思路。
摘要由CSDN通过智能技术生成

赛后总结

这场是第二天开的一场 V P VP VP,赛时写了快速 A A A了三题, D D D题写了一个多小时过不了,不得不感叹 d i v . 1 div.1 div.1三题大佬的强大。

这边也祝贺我校第一个橙名大佬(XSamsara)的诞生。

A. Hard Way

签到,但卡题意。

题意

有一个三角形,给出三个点的坐标,给出不能由 y = 0 y=0 y=0上任意一点在不穿过三角形的情况下不能够到达的边的长度。

题解

这里给出一个三角形,我们能够确定的是更加靠近 y = 0 y=0 y=0的两条边一定能够在不穿过三角形的情况下到达 y = 0 y=0 y=0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AKPr8Gnv-1645797206056)(https://raw.githubusercontent.com/ChikingChen/image/main/Codeforces%20Round%20%23773%20(Div.%202)]%20pic1.jpg)

另外要讨论的是远离 y = 0 y=0 y=0的那条边。如果这条边与 y = 0 y=0 y=0平行,这条边就不能到达,如果这条边不平行,那就能够达到。所以我们只要判定一下平行以及远离就行了。

void MAIN(){
    cin>>x1>>y1>>x2>>y2>>x3>>y3;
    if(y1==y2&&y3<y1) cout<<abs(x1-x2)<<endl;
    else if(y1==y3&&y2<y1) cout<<abs(x1-x3)<<endl;
    else if(y2==y3&&y1<y2) cout<<abs(x2-x3)<<endl;
    else cout<<0<<endl;
    return ;
}

B. Power Walking

题意

给出 n n n个数字,将他们分成 k k k组,每组的权值为他们所拥有的不同的数字种类。请问当 k k k分别为 1 … n 1\ldots n 1n的情况下权值总和最小为多少。

题解

首先确定一组的情况下权值只能是不同数字的个数。然后就是两组,如果有的数字只有一个那就把那一个分出去,因为这种情况下权值不会增加。如果没有单独一个的智能分一个其他的数字出去,这种情况下,权值总和加一。

void MAIN(){
    set<int> st;
    int n;
    cin>>n;
    for(int i=1,num;i<=n&&cin>>num;i++) st.insert(num);
    for(int i=1;i<=n;i++) cout<<max(i,(int)st.size())<<" ";
    cout<<endl;
    return ;
}

C. Great Sequence

题意

给出一个有 n n n个数字的数组以及一个数字 x x x。删除任意个数字,是的这个数组中没有两个数字能够达成 a p = x ∗ a q a_p=x*a_q ap=xaq的关系,请问这个数组最大是多少?

题解

将所有数字存放在一个 m a p map map中,枚举每一个数字,判断他的 x x x倍是否存在,如果存在将这两个数字的 m a p map map对应值都减一,最后输出 m a p map map中剩下的数字之和。

void MAIN(){
    map<int,int> mp;
    int n,x;
    cin>>n>>x;
    for(int i=1,num;i<=n&&cin>>num;i++) mp[num]++;
    int ans=0;
    for(auto &v:mp){
        while(v.second!=0){
            if(mp[v.first*x]==0) break;
            mp[v.first]--;mp[v.first*x]--;
        }
        ans+=v.second;
    }
    cout<<ans<<endl;
    return ;
}

D. Repetitions Decoding

题意

有一个数组里面有 n n n个数字。现在有一类操作,你可以往数组中某一数字的后面插入两个相同的数。要求用 ≤ n 2 \leq n^2 n2次数的操作构造出一个由且仅由多个二次重复的子序列(如 122122 122122 122122)构成的数组。请输出操作的次数,具体的操作方式,构造出的重复子序列的数量以及长度。

题解

首先我们先确定下来当有数字出现过奇数次的时候一定不能够构造出一个符合条件的序列。因为每次插入 2 2 2个数字都是增加偶数个出现次数,结果还是奇数次,奇数个相同的数字不可能构造出重复的子序列。

接下来讲一下怎么构造出目标数组。首先偶数长度的连续相同数字我们可以认为已经构造好了。找到第一个需要构造的数字,然后找到下一个相同的数字,按位匹配,如果有某一位上数字不同则在后面半段对应的位置之前插入对应的数字。

void insert(int pos,int x){
    for(int i=n;i>=pos;i--) num[i+1]=num[i];
    num[pos]=x;n++;
    return ;
}
int find(int start,int x){
    for(int i=start;i<=n;i++) if(num[i]==x) return i;
    return -1;
}
void MAIN(){
    map<int,int> mp1;
    cin>>n;
    for(int i=1;i<=n&&cin>>num[i];i++) mp1[num[i]]++;
    for(auto v:mp1) if(v.second%2!=0){cout<<-1<<endl;return;}
    for(int i=1;i<=n;i++){
        if(num[i]==num[i+1]){i++;continue;}
        int pos=find(i+1,num[i]);
        int x=i;
        for(int j=i+1,k=pos+1;j<pos;j++,k++){
            if(num[j]==num[k]) continue;
            insert(k,num[j]);
            insert(k,num[j]);
            vec1.push_back({k-1,num[j]});
        }
        i=2*pos-x-1;
    }
    cout<<vec1.size()<<endl;
    for(auto v:vec1) cout<<v.first<<" "<<v.second<<endl;
    for(int i=1;i<=n;i++){
        int pos=find(i+1,num[i]);
        int len=pos-i;
        vec2.push_back(len*2);
        i+=2*len-1;
    }
    cout<<vec2.size()<<endl;
    for(auto v:vec2) cout<<v<<" ";
    cout<<endl;
    return ;
}

E. Anonymity Is Important

题意

n n n个人状态未知,有 q q q次操作,第一种操作能够知道某一个区间内是否有病人,第二种操作是询问在当前掌握的信息下某一个人是否为病人。请返回第二种操作的答案,可以是 Y E S YES YES N O NO NO以及 N / A N/A N/A

题解

这题属于道理大家都懂但不知道怎么优化的那种。

一个人什么时候可以被判断为有病?当你已经知道了一个区间有人生病之后其他所有人都被确定为没病,那这个人一定是有病的。那我们要怎么确定一个人没病呢?当被告知所在区间中没人生病,那这个人必然是没病的。

但如果仅仅只是到这个步骤,我们依然无法得出一个时间复杂度在可以接受范围内的算法。

以前我们这种一边获得一边询问的题目一般使用的是在线算法,但考虑到我们在知道确定一些人不是病人之后再去遍历前面区间中有病人的信息我们的时间复杂度可以被卡到 O ( n 2 ) O(n^2) O(n2)。所以我们这次使用的是离线算法,先获得信息,再去判断。

我们先开一个 s e t set set,在里面记录所有到目前为止依然没有被确定为没有患病的患者,如果我们得到的信息是一个区间内没有病人,那么我们二分找到这个 s e t set set中这个区间最小的数字,然后把小于等于右区间所有数字都移出 s e t set set,并标记当前时间,也就是他们被判定为健康的时间。

如果我们接收到的信息是一个区间内有病人或者是询问某一个人是否是有病,我先将它们存放进一个 v e c t o r vector vector,并标记上时间。

在遍历完一遍之后,我们现在已经在一个 v e c t o r vector vector之中存放了一些无法确定的信息并在一个 s e t set set中存放了依然被我们怀疑是病人的人。现在我们要遍历这个 v e c t o r vector vector。如果我们读到的是一个区间内有病人,我们就在 s e t set set中二分找到这个区间的左右端点,如果左右端点是一个对象,换句话说这个区间中只有一个对象被怀疑是病人,那这个人肯定就是病人了。我们将当时的时间与区间内其他节点被确定为健康的时间的最大值取一个最大值就是确定的时间了,这里的最大值维护我用的是线段树,但其实 s t st st表就够用了。当然考虑到可能有多个区间能够确定他是病人,这些最大值我们要取最小值。

现在我们已经确定了所有节点的被确定为健康或不健康的最早时间,现在我们要询问这个节点在某一个时间节点之前是否为病人,那么只要比较一下大小就行了。

void change(int l,int r,int p,int x,int y){//左区间 右区间 当前所在节点 修改位置 修改值
    if(x<l||r<x) return;
    if(l==r){tree[p]=y;return;}
    int mid=(l+r)>>1;
    change(l,mid,lc(p),x,y);
    change(mid+1,r,rc(p),x,y);
    tree[p]=max(tree[lc(p)],tree[rc(p)]);
    return ;
}
int query_segment(int l,int r,int p,int x,int y){//当前左区间 当前右区间 当前节点 目标左区间 目标右区间
    if(y<l||r<x) return 0;
    if(x<=l&&r<=y) return tree[p];
    int mid=(l+r)>>1;
    return max(query_segment(l,mid,lc(p),x,y),query_segment(mid+1,r,rc(p),x,y));
}
void MAIN(){
    cin>>n>>q;
    for(int i=1;i<=n;i++) st.insert(i);
    for(int i=1;i<=q;i++){
        int t;
        cin>>t;
        if(t==0){
            int l,r,x;
            cin>>l>>r>>x;
            if(x==0){
                while(1){
                    auto it=st.lower_bound(l);
                    if(*it>r||it==st.end()) break;
                    healthy[*it]=i;
                    change(1,n,1,*it,i);
                    st.erase(it);
                }
            }else vec.push_back({t,i,l,r,0});
        }else{
            int query;
            cin>>query;
            vec.push_back({t,i,0,0,query});
        }
    }
    for(auto v:vec){
        int x=v.x,t=v.t;//事件类型 0 病人 1 询问 事件时间
        if(x==0){
            int l=v.l,r=v.r;//左区间 右区间
            auto it1=st.upper_bound(r);it1--;//小于等于r的第一个数
            auto it2=st.lower_bound(l);//大于等于l的第一个数
            if(it1!=st.end()&&it2!=st.end()&&*it2==*it1)
                sick[*it1]=min(sick[*it1],max(t,query_segment(1,n,1,l,r)));
        }else{
            int query=v.query;//询问
            if(sick[query]<t) cout<<"YES"<<endl;
            else if(healthy[query]<t) cout<<"NO"<<endl;
            else cout<<"N/A"<<endl;
        }
    }
    return ;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值