poj3253 类似于哈夫曼树,每次选择所有数中最小的两个,所以这里要建造小根堆,并删除最小的两个数,然后求这两个数的和之后再插入,这里用二叉堆的 make_heap(开始要建堆)、heap(中间要调整)、push_heap(要把和插入,但是对于这里,可以简单的求和,放入堆顶,然后调整一下就ok)、pop_heap(对于选择的数删除)
#include <stdio.h>
#define ll long long
#define MAX 20002
int n;
int a[MAX];
void heap(int m)
{
int t;
while(m*2 <= n)
{
m *= 2;
if(m+1 <= n && a[m]>a[m+1]) m ++;
if(a[m] < a[m/2])
{
t = a[m];
a[m] = a[m/2];
a[m/2] = t;
}
else break;
}
}
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);
//建堆
for(int i = n/2; i > 0; i --) heap(i);
ll ans = 0,pre;
while(n > 1)
{
//选择第一个,并且删除
pre = a[1];
a[1] = a[n];
n--;
heap(1);
//再选择第一个
pre += a[1];
//把和放入堆顶 接着调整
a[1] = pre;
heap(1);
ans += pre;//求答案
}
printf("%I64d\n",ans);
return 0;
}
poj2442 题意:有m个序列,每个序列n个数,我们在每个序列中选一个数,那么就有m个数,对应这m个数有一个和,那么对于所有的数,总共有m^n个选法,要求从小到大输出其中最下的n个选法的和
分析:直接暴力时间复杂度太高,这里用到一点dp,对于m个序列,先求出第k-1项序列的n个数,放于A数组,每个数都是k-1个数的和,那么对于第k个序列,把这个序列的第一个和A数组的每一个相加放到B数组,然后把第k个序列的每一个都和A数组的每一个数相加,然后和B中的最大数相比较,如果小,就互换(因为要n个最小的),具体处理如下(用到滚动数组)操作中只要用到一个heap,和最开始的一个make_heap:
//180K 1922MS #include <cstdio> #include <algorithm> using namespace std; #define MAX 2002 int n,m; int a[2][MAX]; //滚动数组 void heap(int r[],int m) { int t; while(m*2 <= n) { m *= 2; if(m +1 <= n && r[m] < r[m+1]) m ++; if(r[m] > r[m/2]) { t = r[m/2]; r[m/2] = r[m]; r[m] = t; } else break; } } int main() { int T; int t,flag; scanf("%d",&T); while(T --) { flag = 0; scanf("%d%d",&m,&n); for(int i = 1; i <= n; i ++) scanf("%d",&a[0][i]); for(int i = n/2; i > 0; i --) heap(a[flag],i); //首先我们把第一个序列建堆 for(int i = 2; i <= m; i ++,flag = 1-flag) { scanf("%d",&t); for(int j = 1; j <= n; j ++) a[1-flag][j] = a[flag][j] + t;//把下一个序列的第一个与上一个序列每一个相加得一个新的序列,也具有堆性质 for(int j = 1; j < n; j ++)//对于这个序列的剩下 n-1个数 与 A 中每一个相加再与 B 的最大比较 { scanf("%d",&t); for(int k = 1; k <= n; k ++) { if(a[flag][k] + t < a[1-flag][1]) { a[1-flag][1] = a[flag][k] + t; heap(a[1-flag],1); //这里就用到堆的性质 } } } } sort(a[flag]+1,a[flag] + n+1); //排序输出 for(int i = 1; i <= n; i ++) { printf(i==1 ? "%d":" %d",a[flag][i]); } printf("\n"); } return 0; }
poj2010 题意:这里有C头牛,每头牛有一个考试分数,和需要资助的钱,我们选择其中的N头牛资助,并且我们只有F的钱,而且我们有一个要求,就是选择的这N头牛,我们需要他们的分数的中位数 最大
处理:既然要分数的中位数,我们就先按照sorce从小到大排序,由于要N个,那么选择第k个牛,前面就有N/2个之后也有N/2个,那么我们只要枚举第N/2~C-N/2的牛即可,又因为要中位数最大,我们就从右到左一一枚举,如果当前这头牛满足条件,那么这头牛的分数就是答案
那么对于满足要求这个条件,我们分别为1~N/2和C-N/2~C 维护left 和right 两个最大堆,left_sum 和 right_sum分别记录左右堆的 和,由于枚举过程中从右到左,那么对于左边的 left需要预处理,left[1]~left[N/2]为维护的对,left[N/2+1]~left[C-N/2] 其中的每个left[i] 表示 枚举i 这头牛的时候,左边选择的N/2头牛的最小资助金和
对于每个点i,left[i]+right_sum+cow[i].fa <= F,就是我们的满足要求
对于每个点i,left[i]+right_sum+cow[i].fa <= F,就是我们的满足要求
(具体操作如下)
#include <cstdio>
#include <algorithm>
using namespace std;
#define MAX 100005
int N,C,F;
struct Cow
{
int s,fa;
bool operator <(const Cow &c)const
{
return s < c.s;
}
} cow[MAX];
int right[MAX],left[MAX],left_sum,right_sum;
void heap(int a[],int n,int m) //最小堆
{
int t;
while(m*2 <= n)
{
m *= 2;
if(m+1 <= n && a[m] < a[m+1]) m ++;
if(a[m] > a[m/2])
{
t = a[m/2];
a[m/2] = a[m];
a[m] = t;
}
else break;
}
}
void make_heap(int a[],int n)
{
for(int i = n/2; i > 0; i --) heap(a,n,i);
}
int main()
{
while(~scanf("%d%d%d",&N,&C,&F))
{
for(int i = 1; i <= C; i ++) scanf("%d%d",&cow[i].s,&cow[i].fa);
//按分数排序
sort(cow+1,cow+C+1);
//先构造左边和右边的 N/2 个数
// 这里注意i 和 j 表示是左右边的牛, 但是我们对于这N/2个数存放left、right都是从下标为1开始,才能维护堆
left_sum = right_sum = 0;
for(int i = 1,j = C-N/2+1; i <= N/2; i ++,j ++)
{
left[i] = cow[i].fa; left_sum += left[i];
right[i] = cow[j].fa; right_sum += right[i];
}
make_heap(left,N/2); //先把左边建堆
//因为一开始我们是从C-N/2 这个位置开始往左边枚举,那么对于这个之前我们要预处理一下
// left[i] 表示枚举i这头牛时,之前的最小的 N/2 个数之和
for(int i = N/2+1; i <= C-N/2; i ++)
{
left[i] = left_sum; //首先这个left值就是 前面的left_sum
//如果这头牛比之前建堆的 N/2头牛的最大要小,就更新堆,为下一头牛的left_sum准备
if(cow[i].fa < left[1])
{
// 把最大牛减掉 加上新加入的牛
left_sum = left_sum - left[1] + cow[i].fa;
left[1] = cow[i].fa;//更新 调整
heap(left,N/2,1);
}
}
make_heap(right,N/2);
int ans = -1;
for(int i = C-N/2; i > N/2; i --) //开始枚举
{
if(right_sum+left[i]+cow[i].fa <= F) { ans=cow[i].s; break;}//这头牛满足
//如果上面不满足,那么就要往左边移,那么对于当前这头牛就要放到右边的堆当中去
if(cow[i].fa < right[1])
{
right_sum = right_sum-right[1]+cow[i].fa;
right[1]=cow[i].fa;
heap(right,N/2,1);
}
}
printf("%d\n",ans);
}
return 0;
}
poj3481
题意:银行处理系统,每一个顾客有一个标记,还有一个优先级,对于操作1,就是添加一个顾客,操作2,服务优先级高的顾客,操作3 服务优先级低的顾客
分析,分维护一个最大堆,和一个最小堆,处理相对于的服务的时候,比如 操作2,我们在最大堆中删除,并且将次顾客标记处理啦,那么对于后面操作3处理过程中如果也是那个顾客,就可以判断是否标记,标记了就先删除,再处理下一个
//1760K 204MS #include <stdio.h> #include <string.h> #define MAX 1000005 struct node { int k,p; }; node h[MAX],l[MAX]; //最大最小堆 int n1,n2; //两个堆的人数 bool flag[MAX]; //标记 void push1(int k,int p)/ { h[++n1].k = k; h[n1].p = p; int i = n1; while(i>1 && h[i/2].p<p) { h[i].k = h[i/2].k; h[i].p = h[i/2].p; i /= 2; } h[i].k = k; h[i].p = p; } void push2(int k,int p) { l[++n2].k = k; l[n2].p = p; int i = n2; while(i>1 && l[i/2].p>p) { l[i].k = l[i/2].k; l[i].p = l[i/2].p; i /= 2; } l[i].k = k; l[i].p = p; } void heap1(int m) { int k,p; while(m*2 <= n1) { m*=2; if(m+1<=n1 && h[m].p<h[m+1].p) m++; if(h[m].p>h[m/2].p) { k = h[m].k; p = h[m].p; h[m].k = h[m/2].k; h[m].p = h[m/2].p; h[m/2].k = k; h[m/2].p = p; } else break; } } int get1() { while(n1>0 && flag[h[1].k]) { h[1].k = h[n1].k; h[1].p = h[n1--].p; heap1(1); } if(n1 == 0) return 0; else { flag[h[1].k] = 1; return h[1].k; } } void heap2(int m) { int k,p; while(m*2 <= n2) { m*=2; if(m+1<=n2 && l[m].p>l[m+1].p) m++; if(l[m].p<l[m/2].p) { k = l[m].k; p = l[m].p; l[m].k = l[m/2].k; l[m].p = l[m/2].p; l[m/2].k = k; l[m/2].p = p; } else break; } } int get2() { while(n2>0 && flag[l[1].k]) { l[1].k = l[n2].k; l[1].p = l[n2--].p; heap2(1); } if(n2 == 0) return 0; else { flag[l[1].k] = 1; return l[1].k; } } int main() { memset(flag,false,sizeof(flag)); int a,k,p; n1=n2=0; while(~scanf("%d",&a)) { if(!a) continue; if(a == 1) { scanf("%d%d",&k,&p); flag[k] = 0; push1(k,p);push2(k,p); } else if(a == 2) { printf("%d\n",get1()); } else { printf("%d\n",get2()); } } return 0; }
个人愚昧观点,欢迎指正与讨论