【暑假专题训练#数据结构】

HDU 2492 Ping pong (树状数组+逆序数 2008 Regional Beijing)

【题目链接】:click here~~

【题目大意】

每一个人都有一个实力值ranking,顺序就是他的位置,要求最多的比赛,比赛要求两个队友,一个裁判,要求裁判的实力再他们之间,而且位置要在他们两人的中间,这样可以组成一场比赛。问总共可以组织多少场比赛?

【思路】:此题要用到树状数组(BIT):

BIT 是这样的一种数据结构:

给定的一个初始值为0 的数列a1,a2....

1:给定i:计算a1+a2+..ai;

2:给定i和x:执行ai+=x

记得第一次做树状数组的时候还是很早很早以前~~,现在写起来发现还是很不熟练,树状数组可以实现的功能线段树基本都能实现,但反过来就不一定,但是都说树状数组效率比较高,因为BIT运用编号的二进制表示就能和区间非常容易的对应起来,从而利用这个性质,用简单的位运算实现

回来看这道题,假设a[i]以自己为裁判,然后找左边有多少人比自己小,再找右边有多少人比自己大,然后两个数相乘就是以他为裁判的比赛场数,当然还有相反的,找右边比自己小的,左边比自己大的,再相乘相加。

而快速找到多少小或大的个数就要发挥BIT 的作用

定义:

LL left_low[N],left_big[N];
LL right_low[N],right_big[N];

那么答案就是  res+=left_low[i]*right_big[i]+left_big[i]*right_low[i];

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
typedef long long LL;

int lowbit(int x){return x&(-x);}
LL num[N];
LL bit[N];
LL left_low[N],left_big[N];
LL right_low[N],right_big[N];
LL n;

LL sum(int i){
    LL s=0;
    while(i>0){
        s+=bit[i];
        i-=lowbit(i);
    }
    return s;
}

void add(int i,int x){
    while(i<=N){
        bit[i]+=x;
        i+=lowbit(i);
    }
}

int main(){
    int t;scanf("%d",&t);
    while(t--){
        scanf("%lld",&n);
        for(int i=1; i<=n; ++i){
            scanf("%lld",&num[i]);
        }
        LL res=0;
        memset(bit,0,sizeof(bit));
        for(int i=1; i<=n; ++i){
            add(num[i],1);
            left_big[i]=sum(N)-sum(num[i]);
            left_low[i]=sum(num[i]-1);
        }
        memset(bit,0,sizeof(bit));
        for(int i=n; i>=1; --i){
            add(num[i],1);
            right_big[i]=sum(N)-sum(num[i]);
            right_low[i]=sum(num[i]-1);
        }
        //  left_lower[i]:输入的前i个数比num[i]小的数的个数
        //  n-i-right_lower[i]:num[i]后输入的那些数中比num[i]大的数的个数
        //  right_lower[i]:输入的前i个数比num[i]大的数的个数
        //  i-left_lower[i]-1:num[i]后输入的那些数中比num[i]小的数的个数
        for(LL i=1; i<=n; ++i){
            //res+=(left_lower[i]*(n-i-right_lower[i])+right_lower[i]*(i-left_lower[i]-1));
           res+=left_low[i]*right_big[i]+left_big[i]*right_low[i];
        }
        printf("%lld\n",res);
    }
    return 0;
}

HDU 1806   Frequent values (离散化+RMQ)

【题目链接】:click here~~

【题目大意】:

 给定一个长度为N(N <= 100000)的单调不降序列Si,然后是Q(Q <= 100000)条询问,问给定区间出现最多的数的次数。

【思路】:这是一道比较考查思维的一个题 

由于Q很大,询问的复杂度必须要log(n),这样一来就先确定下来是线段树了,这个题目有个限制条件,所有数都是单调不增的排列的,换言之,就是说如果两个数相同的话,他们之间的所有数必定也和它们相同。于是就有了O(Q log(n))(用RMQ就是O(Q)了)的算法:

对于所有的数均设定一个组号,也可以叫离散化吧,相同的数有相同的组号,然后将各个数的频率统计后记录在一个数组中,表示该类数的大小,对于输入的询问[x, y],直接查询它们在哪个组,分三种情况讨论:

   1) 如果x和y在一个组,那么最长长度就是y - x + 1

   2) 如果组号相差1,那么找出两者的分界点z(假设z点和x点组号相同),那么答案就是Max{z - x + 1, y - z}

   3) 如果相差大于1,那么先将两头截掉,统计大的记录,再和中间那段的最大值比较大小,中间那段的最大值可以用线段树 或者 RMQ区间查询最值。

代码(RMQ):

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N=2e5+10;
LL n,q;
LL bit[N],cnt[N];
LL num[N];
LL dpmax[N][20];///max
LL dpmin[N][20];///min

struct node    ///记录组数的前后位置
{
    int beg,end;
}line[N];

void getline(){ ///把相同的数分成一组,然后进行编号,“离散化”的过程  //O(N)
    memset(cnt,0,sizeof(cnt));///记录组的数组
    int k=1;int tmp=num[1];
    num[1]=1;cnt[1]=1;
    line[1].beg=1;
    for(int i=2; i<=n; ++i){
        if(num[i]!=tmp){/// appear the new sequence
            tmp=num[i];
            line[k].end=i-1;
            num[i]=++k;
            cnt[k]++;
            line[k].beg=i;
        }
        else{ ///tmp==num[i]
            num[i]=k;
            cnt[k]++;
        }
    }
    line[k].end=n;
}

void RMQ_init(){ ///RMQ
    for(int i=1; i<=n; ++i) dpmax[i][0]=cnt[i];
    double limit=log(n)/log(2.0);
    for(int j=1; j<=(int)limit; ++j)
        for(int i=1; i+(1<<j)-1<=n; ++i){
            dpmax[i][j]=max(dpmax[i][j-1],dpmax[i+(1<<(j-1))][j-1]);
           // dpmin[i][j]=min(dpmin[i][j-1],dpmin[i+(1<<(j-1))][j-1]);
        }
}

LL getmax(LL a,LL b){
    LL k=(int)(log(b-a+1)/log(2.0));
    return max(dpmax[a][k],dpmax[b-(1<<k)+1][k]);
}
LL mid_max;

int main()
{
    while(scanf("%lld",&n)!=EOF&&n){
        scanf("%lld",&q);
        for(int i=1; i<=n; ++i) scanf("%lld",&num[i]);
        getline();
        RMQ_init();
        while(q--){
            LL a,b;
            scanf("%lld%lld",&a,&b);
            if(num[a]==num[b]) printf("%lld\n",b-a+1);
            else if(num[a]==num[b]-1){
                printf("%lld\n",max(line[num[a]].end-a+1,b-line[num[a]].end));
            }
            else{ //a,b 所在的组相差大于 1
                mid_max=getmax(num[a]+1,num[b]-1);//中间最值
                printf("%lld\n",max(mid_max,max(line[num[a]].end-a+1,b-line[num[b]].beg+1)));
            }
        }
    } return 0;
}

POJ 2431 Expediton  (贪心+优先队列)

【题目链接】:click here~~

【题目大意】:

一辆卡车要行驶L单位距离。最开始时,卡车上有P单位汽油,每向前行驶1单位距离消耗1单位汽油。如果在途中车上的汽油耗尽,卡车就无法继续前行,即无法到达终点。途中共有N个加油站,加油站提供的油量有限,卡车的油箱无限大,无论加多少油都没问题。给出每个加油站距离终点的距离和能够提供的油量,问卡车从起点到终点至少要加几次油?如果不能到达终点,输出-1。
【思路】:由于N比较大,应该找一个高效的解法。稍微转换一下思考方式:在卡车开往终点的途中,只有在加油站才可以加油。但是,如果认为“在到达加油站i时,就获得了一次在之后的任何时候都可以加Bi单位汽油的权利”,在解决问题上也是一样的。而在之后需要加油时,就认为是在之前经过的加油站加的油就可以了。因为希望加油次数尽可能少,所以当燃料为0了之后再加油是最好的选择。基于贪心的思想,当燃料为0时,选择能加油量最大的加油站。所以可以用一个优先队列来保存经过的加油站的油量,当需要加油时,取出队列中的最大元素即可。

#include <stdio.h>
#include <string.h>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1000005;

struct node{
    int dist,val;/// the dist and the mount of oil
    bool operator < (const node &a) const{
    return dist<a.dist;
    }
}line[N];
int n,l,p;

void solve(){
    int ans=0,pos=0,tank=p;///the primary oil
    priority_queue <int >val;
    for(int i=0; i<=n; ++i){
        int d=line[i].dist-pos; ///
        while(tank<d){
            if(val.empty()){
                puts("-1");
                return;
            }
            tank+=val.top();
            val.pop();
            ans++;
        }
        tank-=d;
        pos=line[i].dist;
        val.push(line[i].val);
    }
    printf("%d\n",ans);
}

void init(){
  scanf("%d",&n);
  for(int i=0; i<n; ++i) scanf("%d %d",&line[i].dist,&line[i].val);
  scanf("%d%d",&l,&p);
  for(int i=0; i<n; ++i) line[i].dist=l-line[i].dist;
  line[n].dist=l;line[n].val=0;
  sort(line,line+n+1);
}

int main(){
    init();
    solve();
    return 0;
}
/*
Sample Input
4
4 4
5 2
11 5
15 10
25 10
Sample Output
2
*/

POJ 3253  Fence Repair  贪心+优先队列

【题目链接】:click here~~

【题目大意】:将一块很长 的木板切割成N块,准备切割的长度分别为L1,L2,L3,,,,每次切割木板的开销是原先木板的长度之和,例如长度21的木板要切割成5,8,8,三块,开销依次为21,13,所以最后开销为21+13=34,求切完最小开销。

【思路】:由于只需要从木板的集合中取出最短的两块,并且把长度为两块木板之和的板加入集合之中即可,因此可用优先队列实现。

代码:

#include <stdio.h>
#include <queue>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=20005;
typedef long long LL;
LL num[N];
LL n;
void solve(){
    priority_queue <int ,vector<int >,greater<int > >val;
    while(!val.empty()) val.pop();
    for(int i=1; i<=n; ++i) val.push(num[i]);
    LL ans=0;
    while(val.size()>1){
        int l1=val.top();val.pop();
        int l2=val.top();val.pop();
        int l3=l1+l2;ans+=(l3);
        val.push(l3);
    }
    printf("%lld\n",ans);
}
void init(){
    scanf("%lld",&n);
    for(int i=1; i<=n; ++i)
        scanf("%lld",&num[i]);
}
int main(){
    init();
    solve();
    return 0;
}
贪心+优先队列

你可能感兴趣的:(数据结构,专题训练)