数据结构与算法实验题 10.1 神谕者

★实验任务 
众所周知,dota 中神谕者的大招可以抵挡所有伤害,但是当大招结束时会一次性结算所有伤害。神谕者在大招期间已经遭受了 n 次伤害,他现在希望知道自己所遭受伤害中的第 k 小伤害值,但是他在思考时会再次遭受到伤害,他已经没办法准确的得知答案。所以他现在来请求你的帮助。 
★数据输入 
 输入第一行包括两个整数 n,m(1<=n,m<=50000)。第二行有 n 个整数,为神谕者已遭受到的伤害。接下来有 m 次询问,每次询问时有两个整数 a,k;当 a 为 0 时意味着神谕者再次受到伤害,则 k 为其所受伤害值;当 a 为 1 是意味着神谕者想知道他所受伤害中的第 k(k 不会超过已受到的伤害次数)小伤害。 
★数据输出 

当 a 为 1 是输出第 k 小伤害值。 

输入示例 
5 5 
1 3 5 7 9 
1 4 
0 6 
1 4 
0 5 
1 4

输出示例 



5


我以为插入的时候k也是50000以内,那就直接fenwick树解决,后来一问,范围好像是Int,那就只好用划分树或者平衡树了。

听同学说插入排序竟然过了。。。我刚也敲了一个,还真。。。。这数据得多水?

方法一:插入排序

一开始先用nlogn排序一次。O(nlogn)
对于每次插入的数进行插入,最坏情况下遍历整个数组(从最后到最开始)O(m*n)

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=100000+10;
int a[MAXN];
int main()
{
	int n,m,i;
	scanf("%d%d",&n,&m);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);

	sort(a,a+n);
	int cmd,k,len=n;
	for(i=0;i<m;i++)
	{
		scanf("%d%d",&cmd,&k);
		if(cmd==1)
			printf("%d\n",a[k-1]);
		else 
		{
			//插入排序
			int j;
			if(len==0)
			{
				a[0]=k;		
				len++;
				continue;
			}
	
			for(j=len;j>=0;j--)
			{				
				if(k < a[j-1])
					a[j]=a[j-1];
				else
				{
					a[j]=k;
					break;
				}
			}
			len++;
		}
	}
}



方法二:离线+划分树

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXM=20;
const int MAXN=100000+10;
int data[MAXM][MAXN], num[MAXM][MAXN], sorted[MAXN];

struct node
{
	int kind;	//kind=0 代表插入 kind=1则为查询
	int k;
}p[MAXN];

void Build(int depth, int L, int R) {
	if (L == R)
		return;
	int same, mid, i, left, right;
	mid = (L + R) >> 1;
	same = mid - L + 1;
	left = L;
	right = mid + 1;
	for (i = L; i <= R; i++) {
		if (data[depth][i] < sorted[mid])
			same--;
	}
	//same用来标记和中间值val_mid 相等的,且分到左孩子的数的个数。
	for (i = L; i <= R; i++) {
		if (data[depth][i] < sorted[mid])
			data[depth + 1][left++] = data[depth][i];
		else if (data[depth][i] == sorted[mid] && same) {
			data[depth + 1][left++] = data[depth][i];
			same--;
		} else
			data[depth + 1][right++] = data[depth][i];
		num[depth][i] = num[depth][L - 1] + left - L;      
		//num记录元素所在区间的当前位置之前进入左孩子的个数
	}
	Build(depth + 1, L, mid);
	Build(depth + 1, mid + 1, R);
}

int findk(int L, int R, int x, int y, int k,int depth) {
	if (L == R)
		return data[depth][L];

	int mid, left, temp;
	mid = (L + R) >> 1;
	left = num[depth][y] - num[depth][x - 1];
	temp = num[depth][x - 1] - num[depth][L - 1];
	if (left >= k)
		return findk( L, mid, L + temp, L + temp + left - 1, k,depth + 1);
	else {
		k -= left;
		temp = x - L - temp;
		left = y - x + 1 - left;
		return findk( mid + 1, R, mid + temp + 1, mid + temp + left, k,depth + 1);
	}
}

int main() {

	int n,m,i;
	int len=0;
	scanf("%d%d", &n,&m);

	for(i=0;i<n;i++)
	{
		scanf("%d",&p[i].k);
		len++;
		sorted[len] = data[0][len] = p[i].k;
		p[i].kind=0;    //insert
	}

	int tot=n+m;

	for(;i<tot;i++)
	{
		int action;
		scanf("%d%d",&action,&p[i].k);
		if(action==0)
		{
			p[i].kind=0;	
			len++;
			sorted[len] = data[0][len] = p[i].k;
		}
		else if(action==1)
		{
			p[i].kind=1;
		}
	}

	sort(sorted + 1, sorted + len + 1);
	Build(0, 1, len);
	
	int cnt=0;			//统计当前元素个数
	for(i=0;i<tot;i++)
	{
		if(p[i].kind==0)
			cnt++;
		else 		
			printf("%d\n",findk(1,len,1,cnt,p[i].k,0));	
	}
	return 0;
}

方法三 带附加信息的BST

记录它的左儿子个数,方便查询。

查询的时候大于左儿子个数向右查询 k=k-num-1,否则向左


#include<cstdio>
struct node
{
	node *left;
	node *right;
	int num;
	int cntL;
	node(){ left=right=NULL; cntL=0; }
};

struct BST
{
	node *root;
	BST() {root=NULL;}
	void insert(int num)
	{
		node *p=root,*p_fa=NULL;
		node *temp=new node;  
		temp->num=num;  

		while(p)  
		{  
			p_fa=p;
			if(num <= p->num) //left;  
			{
				p->cntL++;
				p=p->left;
			}
			else          //right  
				p=p->right;		  
		}  
		
		if(root==NULL)  
        {  
            root=temp;  
            return;  
        }  

		if(num <= p_fa->num) 
			p_fa->left=temp;
		else
			p_fa->right=temp;
	}  

	int find(int k)
	{
		node *p=root;
		while(k!=p->cntL+1)
		{
			if(k> p->cntL)    //大于就往右边查找
			{
				k=k-p->cntL-1;
				p=p->right;
			}
			else
			{
				p=p->left;
			}
		}
		return p->num;
	}
}bst;

int main()
{
	int n,m,i;
	scanf("%d%d",&n,&m);
	int cmd,k;
	for(i=0;i<n;i++)
	{
		scanf("%d",&k);
		bst.insert(k);
	}
	for(i=0;i<m;i++)
	{
		scanf("%d%d",&cmd,&k);
		if(cmd==0)	
			bst.insert(k);
		else
			printf("%d\n",bst.find(k));
	}
	return 0;
}


方法四:SBT

我们都知道,BST在极端情况下会退化成一条链,那么查询效率将会变成O(n)
当然此题数据特别水,插入排序都过了,就不用说BST了。
但是真实情况是我们不知道水不水!
平衡树有AVL、红黑树、treap、SBT
也可以用splay,但是比较慢QAQ
AVL和红黑树写起来比较复杂,推荐treap和SBT
根据创造SBT的作者吹嘘,SBT是最快的,下面介绍SBT

SBT全称叫Size Balanced Tree,也是一种平衡树。

他既不SB也不BT。

关于SBT树我的介绍:Size Balanced Tree(SBT树)整理http://blog.csdn.net/murmured/article/details/17029131

这一次好多人都用这个。。。

#include<cstdio>
const int MAXN=200000+10;
struct SBT
{
	int left[MAXN];   //left son
	int right[MAXN]; //right son
	int size[MAXN];   //the num of sons
	int value[MAXN];  //value
	int len;			//length
	int root;
		SBT(){ root=len=0; }

	void right_rotate(int &t)
	{
		int k=left[t];
		left[t]=right[k];
		right[k]=t;
		size[k]=size[t];
		size[t]=size[ left[t] ] + size[ right[t] ] +1;
		t=k;
	}

	void left_rotate(int &t)
	{
		int k=right[t];
		right[t]=left[k];
		left[k]=t;
		size[k]=size[t];
		size[t]=size[left[t]]+size[right[t]]+1;
		t=k;
	}

	void insert(int &t,int v)
	{
		if(!t)
		{
			t=++len;  
			value[t]=v;  
			size[t]=1;  
			left[t]=right[t]=0;
			return; 
		}
		size[t]++;

		if(v < value[t])
			insert(left[t],v);
		else
			insert(right[t],v);
		
		matain(t);
	}

	void matain(int &t)
	{
		if(size[ left[ left[t] ] ] > size[ right[t] ] )
		{
			right_rotate(t);
			matain(right[t]);
			matain(t);
		}
		else if( size[ right[ left[t] ] ]>size[ right[t] ] )
		{
			left_rotate(left[t]);
			right_rotate(t);
			matain(left[t]);
			matain(right[t]);
			matain(t);
		}
		else if(size[ right[ right[t] ] ]>size[ left[t] ])
		{
			left_rotate(t);
			matain(left[t]);
			matain(t);
		}
		else if(size[ left[ right[t] ] ]>size[ left[t] ])
		{
			right_rotate(right[t]);
			left_rotate(t);
			matain(left[t]);
			matain(right[t]);
			matain(t);
		}
	}

	int select(int t,int k)
	{
		if(k==size[left[t]]+1)
			return value[t];
		if(k<=size[left[t]])
			return select(left[t],k);
		else
			return select(right[t],k-size[left[t]]-1);
	}
}sbt;

int main()
{
	int n,m,i;
	scanf("%d%d",&n,&m);
	int cmd,k;
	for(i=0;i<n;i++)
	{
		scanf("%d",&k);
		sbt.insert(sbt.root,k);
	}

	for(i=0;i<m;i++)
	{
		scanf("%d%d",&cmd,&k);
		if(cmd==0)	
			sbt.insert(sbt.root,k);
		else
			printf("%d\n",sbt.select(sbt.root,k));
	}
	return 0;
}


  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值