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~~
【题目大意】:
#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 */
【题目链接】: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; }