模拟测试20190716

T1:礼物

水题一道,谁爆零谁傻逼,谁考完后5minA谁智障

(天皇:我15min就A了,期望dp不倒着就是傻;DeepinC:你说啥?)

T2:通讯

tarjan模板,先缩点,贪心稍微需要思考,只要对不是0所在强联通分量的点取一条最小入边就好了

(cbx曰:谁爆零谁cbx)

 T3:奇袭 

考试的时候刚开始没思路,就想着打个n^5暴力,然后打着打着发现了一些性质,删了层循环,然后又删了层循环,又删了层循环

然后开心地得到了n^2的解法,64分拿到

然后就死了,并不知道接下来怎么做,64分钉死

这题用到了几个很久之前学过的东西,一个从来没学过的东西

学过的:分治,单调栈  没学过:桶

首先我们观察n^2的式子,if(max(hi,hi+1......hj)-min(hi,hi+1......hj)+1==j-i+1) ans++,hi表示第i行的军队在第几列

由于ans上界10亿,单纯ans++肯定过不了,考虑分治

solve(l,r)表示l到r的区间有多少贡献显然可以以中点为界递归求解,现在我们只需要求解跨mid的情况

这种情况主要分为两种:1,最小值和最大值都在一边

           2,最小值和最大值在两边

对第一种情况,这里以在左边为例,我们利用单调栈(感性理解)的思想,处理出来mid左右每个位置到mid的最大最小值

然后我们扫左边(l,mid)所有点,能得到可能的右端点,然后判断是否成立

即对于左边的一个点i,令j=i+lmax[i]-lmin[i],若rmin[j]>=lmin[i]&&rmax[j]<=lmax[i],还有j>mid&&j<=r

扫到成立的点直接 ans++,单次复杂度O(r-l+1),总复杂度O(nlogn)

然后就是优美(?)的最大最小分边情况了,这里我们只考虑最小值在左,最大值在右的情况

我们发现若一个区间(i,j)满足要求,则有max(hmid+1,hmid+2......hj)-min(hi,hi+1......hmid)==j-i

然后我们把他移项,就得到了max(hmid+1,hmid+2......hj)-j==min(hi,hi+1......hmid)-i

用我们求出来的东西表示即为lmin[i]-i==rmax[j]-j

只要满足最大值在右,最小值在左,那左边和右边无关,这让我们想到了一个东西,桶(虽然我没想到)

我们把右边的东西放进桶里,然后用左边的去查询就好了,现在我们只需要找到每个i对应的合适区间就好了

我们根据max,min的单调性,可以发现每个i对应的区间都是连续的,如果暴力我们可以二分(那就和桶没什么关系了),但是我们发现每个i对应的区间也是单调的

我们可以让两个指针维护当前可行区间,设L为当前左区间,R为当前右区间

先让L=R=mid,然后让L移到对于r最左不可行位置,同时让桶里的值++,然后让R左移到对于r最右可行位置,同时把不符合的情况--

然后从扫(mid+1,mid)每个点,让L,R移到可行位置同时更新桶里的值,如果t[rmax[i]-i]>0,ans+=t[rmax[i]-i]

最后就得到了跨区间的答案,然后我们发现桶太大,如果每次都memset时间太慢

但是我们发现只用(l,mid)的值更新了桶,所以再扫一遍(l,mid),把桶里对应值归零就好了

如果要处理相反情况,可以直接把区间翻转,网上好多都说加reverse会提高复杂度,但是reserve一次复杂度O(len),所以总复杂度没变,记得处理中点

单次复杂度O(r-l+1),总复杂度O(nlogn)

最终复杂度O(nlogn)

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int h[50005],ans,lmax[50005],lmin[50005],rmax[50005],rmin[50005],tong[100005],*t=tong+50000;
inline int get(int l,int r,int mid){
    int ans=0;
    lmax[mid]=lmin[mid]=h[mid],rmax[mid+1]=rmin[mid+1]=h[mid+1];
    for(int i=mid-1;i>=l;i--){
        lmax[i]=max(lmax[i+1],h[i]);
        lmin[i]=min(lmin[i+1],h[i]);
    }
    for(int i=mid+2;i<=r;i++){
        rmax[i]=max(rmax[i-1],h[i]);
        rmin[i]=min(rmin[i-1],h[i]);
    }
    for(int i=l,j=i+lmax[i]-lmin[i];i<=mid;i++,j=i+lmax[i]-lmin[i])
        if(j>mid&&j<=r&&lmax[i]>=rmax[j]&&lmin[i]<=rmin[j]) ans++;
    int R=mid,L=mid;
    while(R>=l&&lmin[R]>rmin[r])
        t[lmin[R]-R]--,R--;
     while(L>=l&&lmax[L]<rmax[r])
        t[lmin[L]-L]++,L--;
    for(int i=r;i>=mid+1;i--){
        while(L<mid&&lmax[L+1]>rmax[i]) 
            L++,t[lmin[L]-L]--;
        while(R<mid&&lmin[R+1]<rmin[i]) 
            R++,t[lmin[R]-R]++;
        if(t[rmax[i]-i]>0) 
            ans+=t[rmax[i]-i];
    }
    for(int i=l;i<=mid;i++)
        t[lmin[i]-i]=0;
    return ans;
}
int solve(int l,int r){
    if(l==r) return 1;
    int mid=l+r>>1;
    int ans=solve(l,mid)+solve(mid+1,r)+get(l,r,mid);
    reverse(h+l,h+r+1);
    ans+=get(l,r,mid-((r-l+1)&1));
    reverse(h+l,h+r+1);
    return ans;
}
int main(){
    int n,x,y;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&x,&y),h[x]=y;
    printf("%d",solve(1,n));
    return 0;
}
AC代码

 

转载于:https://www.cnblogs.com/mikufun-hzoi-cpp/p/11196707.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值