Codeforces Round #703 (div2)

A. Shifting Stacks (*900)

题意

\(n(1\leq n\leq 100)\)堆块,每堆有一个高度\(h_i(0\leq h_i\leq 1e9)\),可以从第\(i\)堆移动一个块到第\((i+1)\)堆,能否实现整个序列严格单调递增?

题解

严格单调递增时,前\(i\)堆总和最小时,高度依次为\(0,1,2,3,\cdots\),总和最小值\(\frac{i(i-1)}{2}\)。所以只需要检查每个位置的前缀和是否大于等于\(\frac{i(i-1)}{2}\)

#include<bits/stdc++.h>
#define LL long long
#define lowbit(x) x&(-x)
#define eps 1e-6
using namespace std;

const int maxn=110;

int T,n;
LL a[maxn];

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        LL need=0,sum=0;
        bool f=true;
        for(int i=1;i<=n;i++){
            need+=i-1;
            sum+=a[i];
            if(sum<need){
                f=false;
                break;
            }
        }
        printf("%s\n",f?"YES":"NO");
    }
}

B. Eastern Exhibition (*1500)

题意

二维平面上有\(n(1\leq n\leq 1000)\)座房屋,每座房屋有横纵坐标\(x_i,y_i(0\leq x_i,y_i\leq 1e9)\),计算平面上到所有房屋的曼哈顿距离最小的点的个数

题解

首先,问题不是二维的,因为两个一维互不影响,所以可以转化为对一维求解,再将答案相乘
对于一维情况,也就是一条直线上的点,有已知结论:总数为奇数时,中间的点符合条件,总数为偶数时,中间的两个点以及它们中间的点符合条件

#include<bits/stdc++.h>
#define LL long long
#define lowbit(x) x&(-x)
#define eps 1e-6
using namespace std;

const int maxn=1010;
int T,n;
LL x[maxn],y[maxn];

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%lld %lld",&x[i],&y[i]);
        sort(x+1,x+1+n);
        sort(y+1,y+1+n);
        LL ans1=1,ans2=1;
        if(n%2==0){
            ans1=(x[n/2+1]-x[n/2])+1;
            ans2=(y[n/2+1]-y[n/2])+1;
        }
        printf("%lld\n",ans1*ans2);
    }
}

C1. Guessing the Greatest (easy version) (*1600)

题意

交互题
有一个长度为\(n(1\leq n \leq 1e5)\)的数组,每次可以选择一个区间\([l,r](1\leq l,r \leq n)\),询问区间内次大值的位置。在不超过\(40\)次询问中找到数组中最大值的下标

题解

二分
由于\(2^{17}\approx 1e5\),所以使用二分时,需要在\(2\)次询问中确定最大值位于左半边还是右半边。首先询问\([l,r]\)内的次大值的位置,设为\(smax\),之后二分区间\([l,r]\),设\(mid=\frac{(l+r)}{2}\),在两个区间\([l,mid]\)\([mid+1,r]\)中,如果\(smax\)所在区间长度大于\(1\),再询问\(smax\)所在区间的次大值的位置,如果依然是\(smax\),则说明最大值在这个区间中,否则说明最大值在另一个区间中

#include<bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define LD long double
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PLI pair<LL,int>
#define pi acos(-1.0)
#define eps 1e-6
#define lowbit(x) x&(-x)
using namespace std;

int n;

int main(){
    scanf("%d",&n);
    int l=1,r=n;
    while(r>l){
        printf("? %d %d\n",l,r);
        fflush(stdout);
        int smax;
        scanf("%d",&smax);
        int mid=(l+r)>>1;
        if(smax<=mid){
            if(l==mid){
                l=mid+1;
                continue;
            }
            printf("? %d %d\n",l,mid);
            fflush(stdout);
            int t;
            scanf("%d",&t);
            if(t==smax){
                r=mid;
            }
            else l=mid+1;
        }
        else{
            if(mid+1==r){
                r=mid;
                continue;
            }
            printf("? %d %d\n",mid+1,r);
            fflush(stdout);
            int t;
            scanf("%d",&t);
            if(t==smax){
                l=mid+1;
            }
            else r=mid;
        }
    }
    printf("! %d\n",r);
}

C2. Guessing the Greatest (hard version) (*1900)

题意

\(c1\)提高版,在不超过\(20\)次询问中找到数组中最大值的下标

题解

首先通过两次询问找到最大值位于次大值左边还是右边,具体方法是首先找到次大值的位置\(smax\),之后询问\([1,smax]\),如果结果仍为\(smax\),说明最大值位于\([1,smax]\),否则说明最大值位于\([smax+1,r]\)。之后每次经过一次询问二分区间,询问的是以\(smax\)为一个端点,二分另一个端点得到的区间,总询问次数为\(2+\lceil \log_21e5 \rceil=19\)

#include<bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define LD long double
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PLI pair<LL,int>
#define pi acos(-1.0)
#define eps 1e-6
#define lowbit(x) x&(-x)
using namespace std;

int n;

int main(){
    scanf("%d",&n);
    printf("? %d %d\n",1,n);
    fflush(stdout);
    int smax,t;
    scanf("%d",&smax);
    if(smax>1){
        printf("? %d %d\n",1,smax);
        fflush(stdout);
        scanf("%d",&t);
    }
    int l,r;
    if(smax>1 && t==smax){
        l=1;
        r=smax-1;
        while(r>l){
            int mid=(l+r+1)>>1;
            printf("? %d %d\n",mid,smax);
            fflush(stdout);
            int tt;
            scanf("%d",&tt);
            if(tt==smax) l=mid;
            else r=mid-1;
        }
    }
    else{
        l=smax+1;
        r=n;
        while(r>l){
            int mid=(l+r)>>1;
            printf("? %d %d\n",smax,mid);
            fflush(stdout);
            int tt;
            scanf("%d",&tt);
            if(tt==smax) r=mid;
            else l=mid+1;
        }
    }
    printf("! %d\n",r);
}

D. Max Median (*2100)

题意

有一个长度为\(n(1\leq n\leq 2e5)\)的数组\(a\)\(1\leq a_i\leq n\),中位数定义为将一个序列按照递增排序之后下标为\(\lfloor \frac{(n+1)}{2} \rfloor\)的数。给定\(k(1\leq k\leq n)\),计算数组\(a\)中长度至少为\(k\)并且中位数最大的连续子段的中位数的值

题解

二分答案,设二分出的值是\(x\),判断数组中是否存在满足条件,并且中位数大于等于\(x\)的子段
将数组\(a\)处理成只包含\(-1,1\)的数组,方法为如果\(a_i\geq x\),则赋为\(1\),否则赋为\(-1\)。这样如果某个子段的中位数至少是\(x\),等价于子段和为正数
问题转化为寻找长度至少为\(k\),并且子段和最大的子段,判断子段和是否为正数
计算前缀和,扫一遍数组,计算以位置\(i\)结尾的满足条件的子段的最大子段和,具体方法是用\(i\)的前缀和减去\(0,1,\cdots,i-k\)中最小的前缀和
时间复杂度\(O(n\log n)\)

#include<bits/stdc++.h>
#define LL long long
#define lowbit(x) x&(-x)
#define eps 1e-6
using namespace std;

const int maxn=200010;

int n,k,a[maxn],b[maxn];

bool check(int x){
    for(int i=1;i<=n;i++){
        if(a[i]>=x) b[i]=1;
        else b[i]=-1;
    }
    for(int i=1;i<=n;i++) b[i]+=b[i-1];
    int ans=b[k];
    int mini=0;
    for(int i=k+1;i<=n;i++){
        mini=min(mini,b[i-k]);
        ans=max(ans,b[i]-mini);
    }
    return ans>0;
}

int main(){
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int l=1,r=n;
    while(r>l){
        int mid=(l+r+1)>>1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    printf("%d\n",r);
}

E

F

posted @ 2021-02-20 23:57  fxq1304  阅读(21)  评论(0编辑  收藏  举报