某考试题~低头一族 [好题](简要的数学推倒)[四星]

题目描述 Description

一群青年人排成一队,用手机互相聊天。
每个人的手机有一个信号接收指标,第i个人的接收指标设为v[i]。
如果位置在x[i]的人要和位置在xj的人聊天,那么这两人组成的一对的信号发射强度就是abs(x[i]-x[j])*max(v[i],v[j]).
现在我们想知道,这些人所有对子中的信号发射强度的总和。

输入输出格式 Input/output

输入格式:

第一行一个整数N,接下来N行,每行两个整数v[i]和x[i]。

输出格式:

所有对的信号发射强度总和。

输入输出样例 Sample input/output

4
3 1
2 5
2 6
4 3

输出样例:

57

说明 description

对于40%的数据,N<=5,000
对于100%的数据,N<=100,000 1≤x[i]≤20,000
注意:可能有两人在同一个位置
答案在int64或long long范围内

题解

这题是luogu官方考试的一套题目,当时并没有深入思考,交了一份暴力拿了40,最后只拿到了40……………………。
这题是个不错的题,需要仔细想一下。
首先,暴力的解法就是读一个数处理一下,for(j=1->i-1),把ans加上那个值,最后输出ans即可。

但是暴力的复杂度是n^2,n == 10W,显然是不行的,于是我们就要优化一下:
首先是取max操作,每次取max很麻烦,我们不妨直接按v进行从小到大的排序,这样每次都是*v[i]了,不用取max了。
再对abs那个东西进行一些小的变形:
对于第i项,它的x左边的abs就是它本身,用暴力的方法写出来的话就是(x[i]-x[1]+x[i]-x[2]+x[i]-x[3]+……+x[i]-x[j])(这里要特别注意,这里减去的都是x坐标小于x[i]的,并且由于不会再csdn上写数学公式……于是就直接这么写吧,如果想要更详细证明的可以留言~我会付图),再将上面的和式优化一下:x[i]*tot(tot是小于x[i]的坐标个数和)-sigma(x[j]),这样就优化了很多了,下面就是如何快速求得tot了,对于数据范围支持log级别的操作,于是我们很容易想到线段树维护或者树状数组来维护,从代码复杂度来考虑还是打树状数组更优,于是我们可以用一个树状数组来维护前x位总共有几个人,每次查询是log级别的。对于当前人右边部分同样可以用上述的和式优化一下,用log级的时间求出来。听了这么多你一定糊涂了,所以我们来看一下代码吧~
函数部分

void cadd(int a,int x){ for(int i = a;i <= 23333;i += lowbit(i))    c[i] += x;}
int cquery(int a)
{
    int ans = 0;
    for(int i = a;i > 0;i -= lowbit(i)) ans += c[i];
    return ans;
}

主函数部分

int lnum = cquery(poi[i].x-1);//查询到当前点x坐标为止前面有几个人
int rnum = i-cquery(poi[i].x);//查询到当前点坐标为止后面有几个人,因为当前已经加入了i个人,当前坐标(包括它自己)前面有cquery(poi[i].x)个人,相减即得到我们想要的答案。

这里有没有注意我写的是c add,是不是还有b add呢?b add的作用是什么呢?
大家应该注意到我只是快速求得了tot并没有快速求得sigma(x[j]),快速求得sigma(x[j])也是用树状数组:
cadd是直接+1,那么当前坐标的总和就是(当前坐标人数*当前坐标)了,这个我们可以用树状数组来搞,即(badd(x[i],x[i])),用代码实现就是下面这样:

void badd(int a,int x){ for(int i = a;i <= 23333;i += lowbit(i))    b[i] += x;}
int bquery(int a)
{
    int ans = 0;
    for(int i = a;i > 0;i -= lowbit(i)) ans += b[i];
    return ans;
}

然后就是快速求得sigma(x[j]),这里我们的llsum就是左边的坐标和,rsum就是右边的坐标和。

int lsum = bquery(poi[i].x-1);
int rsum = bquery(23333)-bquery(poi[i].x);

最后我们直接相加相减,复杂度O(1)~

sum += (ll)(lnum*poi[i].x-lsum-rnum*poi[i].x+rsum)*(ll)(poi[i].v);

这样这个题就搞定了,我感觉这题思维难度还是不小的,考试的时候苦思冥想不得解,多打思维题提升脑洞能力~
下面附上完整代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
const int size = 200010;
void RD(int &x)
{
    x = 0;  char c; c = getchar();
    int flag = 1;
    if(c == '-')    flag = -1;
    while(c > '9' || c < '0')   c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + c - '0',c = getchar();
    x *= flag;
}
struct Poi{
    int x,v;
    bool operator < (const Poi &rhs) const
    {
        return v < rhs.v;
    }
}poi[size];
int b[size],c[size];
void badd(int a,int x){ for(int i = a;i <= 23333;i += lowbit(i))    b[i] += x;}
int bquery(int a)
{
    int ans = 0;
    for(int i = a;i > 0;i -= lowbit(i)) ans += b[i];
    return ans;
}
void cadd(int a,int x){ for(int i = a;i <= 23333;i += lowbit(i))    c[i] += x;}
int cquery(int a)
{
    int ans = 0;
    for(int i = a;i > 0;i -= lowbit(i)) ans += c[i];
    return ans;
}
ll sum;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)  scanf("%d%d",&poi[i].v,&poi[i].x);
    sort(poi+1,poi+1+n);
    for(int i = 1;i <= n;i ++)
    {
        badd(poi[i].x,poi[i].x);
        cadd(poi[i].x,1);
        int lnum = cquery(poi[i].x-1);
        int rnum = i-cquery(poi[i].x);
        int lsum = bquery(poi[i].x-1);
        int rsum = bquery(23333)-bquery(poi[i].x);
        sum += (ll)(lnum*poi[i].x-lsum-rnum*poi[i].x+rsum)*(ll)(poi[i].v);
    }
    printf("%lld\n",sum);
    return 0;
}

蒟蒻Orz各位大神





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值