题目大意:
题目链接:http://poj.org/problem?id=3784
给出由个数字组成的的数字串,每当输入奇数个时,输出当前的中位数。
思路:
思路一:
如果我们将这个数列从中位数分开,将比中位数小的数放入大根堆,比中位数大的数放入小根堆,中位数也放入大根堆,那么每次读入时,维护两个堆的情况,保持大根堆的元素不比小根堆的元素少且最多多出一个,那么每次输入为奇数个时,中位数为大根堆的对顶。
时间复杂度
思路二:
要求中位数,那么就要现将这个数组排序。那么就可以先得到最终的中位数。然后数据保持单调性,将一个数与左右用链表连接,并记录下原来在这个位置的是哪个数字。然后每次删除最后输入的两个数字(保持数字个数为奇数),然后分类讨论:
- 若且,
- 若且,
- 若且,不变
- 若且,
- 若且,
最终倒着输出即可。
时间复杂度
代码:
对顶堆(手打):
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,a,maxn[12001],minn[12001],ans[12001],tot_max,tot_min;
bool ok;
void up_min(int x) //小根堆上移操作
{
int y=x/2;
while (y>0&&minn[y]>minn[x]) //能往上
{
swap(minn[y],minn[x]); //上移
x=y;
y=x/2;
}
}
void up_max(int x) //大根堆上移操作
{
int y=x/2;
while (y>0&&maxn[y]<maxn[x])
{
swap(maxn[y],maxn[x]);
x=y;
y=x/2;
}
}
void down_min(int x) //小根堆下移操作
{
int y=x*2;
while ((y<=tot_min&&minn[y]<minn[x])||(y+1<=tot_min&&minn[y+1]<minn[x])) //能继续下移
{
if (minn[y+1]<minn[y]&&y+1<=tot_min) y++; //求更优解
swap(minn[x],minn[y]); //下移
x=y;
y=x*2;
}
}
void down_max(int x) //大根堆下移操作
{
int y=x*2;
while ((y<=tot_max&&maxn[y]>maxn[x])||(y+1<=tot_max&&maxn[y+1]>maxn[x]))
{
if (maxn[y+1]>maxn[y]&&y+1<=tot_max) y++;
swap(maxn[x],maxn[y]);
x=y;
y=x*2;
}
}
void push_max() //将小根堆的对顶插入大根堆
{
int a=minn[1]; //取出
swap(minn[1],minn[tot_min]);
minn[tot_min]=0;
tot_min--; //删除
down_min(1); //维护
maxn[++tot_max]=a; //插入
up_max(tot_max); //维护
}
void push_min() //将大根堆的对顶插入小根堆
{
int a=maxn[1];
swap(maxn[1],maxn[tot_max]);
maxn[tot_max]=0;
tot_max--;
down_max(1);
minn[++tot_min]=a;
up_min(tot_min);
}
int main()
{
scanf("%d",&m);
for (int l=1;l<=m;l++)
{
scanf("%d",&n);
scanf("%d",&n);
printf("%d %d\n",l,n/2+1);
memset(maxn,0,sizeof(maxn));
memset(ans,0,sizeof(ans));
memset(minn,0,sizeof(minn));
tot_max=tot_min=0; //初始化
for (int i=1;i<=n;i++)
{
scanf("%d",&a);
if (!tot_max) //堆内没有元素
maxn[++tot_max]=a;
else if (a>maxn[1])
{
minn[++tot_min]=a;
up_min(tot_min);
}
else
{
maxn[++tot_max]=a;
up_max(tot_max);
}
while (tot_min>tot_max) //维护两个堆
{
push_max();
}
while (tot_max>tot_min+1)
{
push_min();
}
if (i%2) //记录答案
{
ans[i/2+1]=maxn[1];
}
}
ok=true;
for (int i=1;i<=n/2+1;i++)
{
if (i%10==1&&i!=1)
{
printf("\n");
ok=false;
}
else ok=true;
printf("%d ",ans[i]);
}
if (ok) printf("\n");
}
return 0;
}
链表:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int m,n,ans[12001],mid,sum;
bool ok;
struct node1 //链表
{
int l,r,num;
}p[12001];
struct node2 //每个数字
{
int a,num,old;
}a[12001];
bool cmp(node2 x,node2 y)
{
return x.a<y.a;
}
void find() //求出一个位置在没有排序之前的位置
{
for (int i=1;i<=n;i++)
a[a[i].old].num=i;
}
void make() //建链表
{
for (int i=1;i<n;i++)
{
p[i].r=i+1;
p[i+1].l=i; //将i和i+1连接
p[i].num=i; //记录编号
}
p[n].num=n;
}
void Delete(int x) //删除节点
{
p[p[x].l].r=p[x].r;
p[p[x].r].l=p[x].l;
}
void play() //求答案
{
int k=n;
int one,two=0;
while (k>0)
{
one=a[k--].num;
two=a[k--].num; //最后输入的两个数
if (one<mid&&two<mid)
mid=p[mid].r;
else if (one>mid&&two>mid)
mid=p[mid].l;
else if ((one==mid&&two>mid)||(two==mid&&one>mid))
mid=p[mid].l;
else if ((one==mid&&two<mid)||(two==mid&&one<mid))
mid=p[mid].r;
Delete(one);
Delete(two); //删除
ans[++sum]=a[mid].a; //记录中位数
}
}
void print() //输出
{
for (int i=1;i<sum;i++)
{
if (i%10==1&&i>1)
{
printf("\n");
ok=false;
}
else ok=true;
printf("%d ",ans[sum-i]); //由于是倒着记录的,所以要倒过来
}
printf("\n");
}
int main()
{
scanf("%d",&m);
for (int l=1;l<=m;l++)
{
scanf("%d",&n);
scanf("%d",&n);
printf("%d %d\n",l,n/2+1);
memset(ans,0,sizeof(ans));
sum=0;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i].a);
a[i].old=i; //记录以前编号
}
sort(a+1,a+1+n,cmp); //排序
mid=n/2+1;
ans[++sum]=a[mid].a;
find();
make();
play();
print();
}
return 0;
}