Atcoder&CodeForces杂题11.7

Preface

又自己开了场CF/Atcoder杂题,比昨天的稍难,题目也更有趣了

昨晚炉石检验血统果然是非洲人...

希望这是给NOIP2018续点rp吧

A.CF1068C-Colored Rooks

现在还没理解题意...

B. CF1070K-VideoPosts

一道模拟,没什么好说的.

不过一开始还是WA了...一个智障的坑,感觉做题还是不细心

const int maxn=100005;
const int inf=0x7fffffff;
int a[maxn],ave,n,k,cnt=0;
int ans[maxn],tot=0;
ll sum=0;
int main(){
    read(n),read(k);
    for(ri i=1;i<=n;i++){
        read(a[i]);
        sum+=a[i];
    }
    if(sum%k!=0){
        puts("No");
        return 0;
    }
    ave=sum/k;
    sum=0;
    for(ri i=1;i<=n;i++){
        sum+=a[i],cnt++;
        if(sum==ave){
            ans[++tot]=cnt;//printf("%d ",cnt);
            sum=cnt=0;
        }
        else if(sum>ave){
            puts("No");
            return 0;
        }
    }
    puts("Yes");
    for(ri i=1;i<=tot;i++)printf("%d ",ans[i]);
    puts("");
    return 0;
}

C. CF1036D-VasyaAndArrays

一看就是一道贪心题,很多人都会猜测就是两个指针不断拓展,如果两个数相等就不加.

想了想发现似乎是正确的,但是不会严格证明,交了发居然A了

const int maxn=300005;
const int inf=0x7fffffff;
int n,m;
int a[maxn],b[maxn];
ll sum1=0,sum2=0;
int main(){
    read(n);
    for(ri i=1;i<=n;i++)read(a[i]),sum1+=a[i];
    read(m);
    for(ri i=1;i<=m;i++)read(b[i]),sum2+=b[i];
    if(sum1!=sum2){
        puts("-1");
        return 0;
    }
    int len=0,pos1=1,pos2=1;
    sum1=a[1],sum2=b[1];
    while(1){
        while(sum1!=sum2){
            if(sum1>sum2)sum2+=b[++pos2];
            else sum1+=a[++pos1];
        }
        len++;
        if(pos1==n&&pos2==m)break;
        sum1=a[++pos1],sum2=b[++pos2];
    }
    printf("%d\n",len);
    return 0;
}

D. CF1038D-Slime

非常有意思的题,一开始想区间DP,但是发现好像是有后效性的,交了发果然WA了

然后只能往贪心这方面想

偶然发现样例非常有意思,一个全正数, 一个全负数+0.

易知如果全是正数的话就可以贪心,你用最小的史莱姆吃掉出最大值以外的所有数,然后最大值吃掉这个数肯定是最优的.这样子答案就是sum-mi-mi

如果是全负数加上一个0,我们就是用0吃掉所有负数显然是最优的

那如果既有非负数又有负数,我们可以用负数减去除最大值以外所有非负整数,然后最大值吃掉所有负数就是最优的,这样子就是所有元素之和

但是如果全都是负数按照全都是正数的思路就是sum-mx-mx

const int maxn=500005;
const int inf=1e9;
int n,a[maxn],mi=inf,mx=-inf;
int main(){
    bool flag=0,flag_2=0;
    ll sum=0;
    read(n);
    if(n==1){
        read(a[1]);
        printf("%d\n",a[1]);
        return 0;
    }
    for(ri i=1;i<=n;i++){
        read(a[i]);
        mi=min(a[i],mi);
        mx=max(a[i],mx);
        sum+=abs(a[i]);
        if(a[i]<0)flag=1;
        if(a[i]>=0)flag_2=1;
    }
    if(flag){
        if(flag_2)printf("%lld\n",sum);
        else printf("%lld\n",sum+mx*2);
    }
    else {
        printf("%lld\n",sum-mi*2);
    }
    return 0;
}

E. Atcoder4167-Equal Cut

一道ARC的B题就不会做了,太菜了...

感觉solution很妙啊,枚举中间的断点.这时候分成了L,R两部分,显然我们还要把L,R分成L1,L2和R1,R2两部分

这时候有一个结论就是让abs(L1-L2)和abs(R1-R2)尽量小一定是最优方案,这个还是比较显然的

这时候可以发现我们在枚举中间断点的时候分割L1,L2的断点和分割R1,R2的断点都是单调的

这里的断点是指abs(L1-L2)和abs(R1-R2)尽量小的断点

于是我们就可以\(O(N)\)扫一遍得出答案了

同时发现一个很坑的地方:

std::abs()而不是abs(),因为差点没查出错来

const int maxn=1000005;
const int inf=0x7fffff;
int n;
ll a[maxn];
ll sum[maxn],mi=1e18,mx=-1e18,ans=1e18,tmp;
int main(){
    int x,y;
    read(n);
    sum[0]=0;
    for(ri i=1;i<=n;i++)read(a[i]),sum[i]=sum[i-1]+a[i];
    int l=1,r=3;
    for(ri k=2;k<=n-2;k++){
        while((abs((sum[k]-sum[l+1])-(sum[l+1]))<abs(sum[k]-sum[l]-sum[l]))&&l+1<k)l++;
        while((abs((sum[n]-sum[r+1])-(sum[r+1]-sum[k]))<abs((sum[n]-sum[r])-(sum[r]-sum[k])))&&r+1<n)r++;
        mi=1e18,mx=-1e18;
        tmp=sum[k]-sum[l];
        mi=min(mi,tmp),mx=max(mx,tmp);
        tmp=sum[l];
        mi=min(mi,tmp),mx=max(mx,tmp);
        tmp=sum[n]-sum[r];
        mi=min(mi,tmp),mx=max(mx,tmp);
        tmp=sum[r]-sum[k];
        mi=min(mi,tmp),mx=max(mx,tmp);
        ans=min(ans,mx-mi);
    }
    printf("%lld\n",ans);
    return 0;
}

F. Atcoder4351-Median Of Medians

这题考试的时候也不会做,考试后发现正解简直太妙了

首先我们需要知道一个性质:如果\(x\)是一个长度为\(N\)的序列的中位数,那么小于等于x的数的个数至少有\(N/2+1\)

于是我们不妨二分一下最后的中位数是哪个,然后我们只需要知道有多少个区间的中位数是小于等于我们二分的这个数就可以了

这里就需要一个高端操作:我们将原序列中小于等于这个数的数置为1,否则置为-1;这样操作之后可以发现如果一个区间的中位数小于等于x,那么这个区间之和肯定大于0.所以我们转化成有多少个区间之和大于0.

我们对这个新序列求一遍前缀和,我们都知道区间\([L,R]\)的和等于\(sum[R]-sum[L-1]\),如果想要这个区间和大于0,实际上就是\(sum[R]>sum[L-1]\),又由于\(R>L-1\),那么我们发现区间之和大于0的区间个数实际上就是新序列前缀和后的顺序对个数,我们离散化之后按照逆序对套路处理即可

但是注意这时候我们没有计算类似\([1,R]\)的区间,这个直接记录一下就好了

但是回到前面的话题,我们已经知道了有多少个区间的中位数小于等于当前二分的这个数,我们该如何更改这个二分边界呢?

还是一开始那个性质,由于区间个数总共有\(N \times (N+1)/2\)个,所以大于等于二分的这个数的区间个数如果大于等于\(N \times (N+1)/4+1\)的话,则将二分右边界左移,否则左边界右移

注意一开始\(N \times (N+1)\) 前没打1ll,结果爆int了,查了好久的错...

const int maxn=200005;
const int inf=0x7fffffff;
int n,nn,a[maxn],b[maxn],c[maxn],mx=-inf,mi=inf;
ll sum[maxn<<2];
inline void update(int x,int d){for(;x<=nn;x+=x&(-x))sum[x]+=d;}
inline ll query(int x){ll ans=0;for(;x;x-=x&(-x))ans+=sum[x];return ans;}
inline ll chk(int k){
    ll ans=0;
    b[0]=0;
    for(ri i=1;i<=n;i++){
        b[i]=b[i-1]+((a[i]<=k)?1:-1);
        c[i]=b[i];
        ans+=(b[i]>0);//记录[1,R]的区间
    }
    std::sort(c+1,c+1+n);
    nn=std::unique(c+1,c+1+n)-(c+1);
    memset(sum,0,sizeof(sum));
    for(ri i=1;i<=n;i++){
          b[i]=lower_bound(c+1,c+1+nn,b[i])-c;//离散化
        ans+=query(b[i]-1);
        update(b[i],1);
    }
    return ans;
}
int main(){
    read(n);
    for(ri i=1;i<=n;i++){
        read(a[i]);
        mi=min(mi,a[i]),mx=max(mx,a[i]);
    }
    int L=mi,R=mx,mid,ans;
      ll lim=1ll*n*(n+1)/4+1;
    while(L<=R){
        mid=(L+R)>>1;
        if(chk(mid)>=lim)ans=mid,R=mid-1;
        else L=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Rye-Catcher/p/9932436.html