暑假嗨11

https://vjudge.net/contest/386618#problem/A

A

题意:
给定一串数,问每一个数最右边的小于这个数和这个数之间的距离是多少,如果没有输出-1.
我的思路:
我是用数状数组来解决的。
struct skr
{ ll v;
int i;
}a[N];
1.先按给定的顺序给每个数一个序号id
for(int i=1;i<=n;i++)
{cin>>a[i].v;a[i].i=i;}
2.将这个数组按照v从小到大排序
3.从左到右遍历排好序的数组(设当前遍历到的数下标为i)
4.add(a[i].i,1)
5.然后记录最右边的位置ma=max(a[i].i,ma)
6.如果sum(n)-sum(a[i].i)!=0的话,说明在这个数的右边有比它小的数,这个数肯定就是ma(因为ma是最靠右)
7.用ma减去a[i].id就可以了
这是我的思路,但是错了,我去codeforce上提交了一次,想看看错误数据,结果对了几千数据,卡在某一个位置出错了,最怕这种情况了。我又想了好长时间,都不知道是怎么错的。我真不知道是哪里错了,去网上找有没有是用树状数组做的,结果找不到,全都是用线段树做的,难道这个题真的不能用树状数组做吗?我觉得可以啊,反正现在是想不出来了,先写下来,等脑子清醒点再想一想吧。

//问题代码:
#include<bits/stdc++.h>
using namespace std;
#define mod 1e9+7
#define N 1000005
#define inf 0x3f3f3f3f
const double PI = atan(1.0)*4.0;
typedef long long ll;
ll b[N],p[N],c[N];
int n;
struct skr
{
    ll i,v;
}a[N];
int lowbit(int x)
{
    return x&(-x);
}
int add(int pos,int v)
{
    while(pos<=n)
    {
        c[pos]+=v;
        pos+=lowbit(pos);
    }
}
ll sum(int pos)
{
    ll ans=0;
    while(pos>=1)
    {
        ans+=c[pos];
        pos-=lowbit(pos);
    }
    return ans;
}
bool cmp(skr a,skr b)
{
    return a.v<b.v;
}
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
   // freopen("E:\\in.txt","r",stdin);

cin>>n;
for(int i=1;i<=n;i++)
    {cin>>a[i].v;a[i].i=i;}
    sort(a+1,a+1+n,cmp);
    ll ma=-1;
    for(int i=1;i<=n;i++)
    {
        add(a[i].i,1);
        ma=max(ma,a[i].i);
        //cout<<sum(n)<<" "<<sum(a[i].i)<<" "<<sum(n)-sum(a[i].i)<<endl;
        if(sum(n)-sum(a[i].i)==0)
            b[a[i].i]=-1;
        else
        {
            b[a[i].i]=ma-a[i].i-1;
        }
    // cout<<"b"<<a[i].i<<":"<<b[a[i].i]<<endl;
    }
for(int i=1;i<=n;i++)
    cout<<b[i]<<" ";
}

然后就是最普遍的线段树做法
每个节点表示该段的最小值, 循环一遍数组,每个位置找到最靠右且比它小的数后(已经处理过),将其改为INF,并对线段树进行最小值更新。

查询之前, 先询问整个数组的最小值和当前值的比较,满足最小值小于当前值才有查询的必要,如果满足查询的条件,右边不成立就一定在左边。

D

题意:
给出一个长度为 n 的递增集合,现在要在集合中取出 3 个数,使得最大数与最小数的差值小于 d,问有多少种取法
思路:
我们只要求出第一个和第三个的数符合题意,中间有多少数就有多少种情况
对于第i个位置,我们用二分来求满足a[j]-a[i]的最右位置和最左位置,这两个数之间的数都满足题意。为什么要用二分呢?因为如果用双重循环的话,最坏情况是1010超时。
线段树用来求某一段的所有数的个数和
我们用它来求最左边满足题意的数与a[i]之间(不包括边界)的数的个数,利用推到的公式算就好了

#include<bits/stdc++.h>
using namespace std;
#define mod 1e9+7
#define N 100005
#define inf 0x3f3f3f3f
const double PI = atan(1.0)*4.0;
typedef long long ll;
ll b[N],p[N],c[N];
ll n,d;
struct skr
{
    ll data;
    int l,r;
}a[4*N];
void build(int node,int start,int endd)
{
     a[node].l=start,a[node].r=endd;
    if(start==endd)
    {
        a[node].data=1;
        return;
    }
    int mid=(start+endd)>>1;
    int L_tree=2*node;
    int R_tree=2*node+1;
    build(L_tree,start,mid);
    build(R_tree,mid+1,endd);
    a[node].data=a[L_tree].data+a[R_tree].data;
}
ll qury(int node,int L,int R)
{ ll ans=0;
    if(a[node].l>=L&&a[node].r<=R)
    {
        return a[node].data;
    }
    if(a[node].r<L||a[node].l>R)
        return 0;
    int mid=(a[node].l+a[node].r)>>1;
        if(a[node*2].r>=L)
        ans+=qury(node*2,L,R);
        if(a[node*2+1].l<=R)
        ans+=qury(node*2+1,L,R);
       return ans;
}
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
  //  freopen("E:\\in.txt","r",stdin);
    cin>>n>>d;
    for(int i=1;i<=n;i++)
        cin>>b[i];
     build(1,1,n);

     ll sum=0;
     for(int i=1;i<=n-2;i++)
     {
         int l=i+2,r=n,mid,pl=inf,pr=inf,ans=0;
         while(l<=r)
         {
             mid=(l+r)>>1;
             if(b[mid]-b[i]<=d)
             {
                 r=mid-1;
                 pl=mid;
             }
             else
                 r=mid-1;

         }
         if(pl!=inf)
         {
            l=i+2,r=n;
         while(l<=r)
         {
             mid=(l+r)>>1;
             if(b[mid]-b[i]<=d)
             {
                 l=mid+1;
                 pr=mid;
             }
             else
                 r=mid-1;

         }
         }
         
         if(pl!=inf)
         {

           sum+=(pr-pl+2*qury(1,i+1,pl-1))*(pr-pl+1)/2;
         }





     }
     cout<<sum<<endl;


}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值