0
点赞
收藏
分享

微信扫一扫

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


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;
}


贪心+优先队列

举报

相关推荐

0 条评论