FZU ACM寒假第三讲

1. Priority Queue

此题为优先队列的模版题,要求我们进行插入和输出最大值的操作,代码如下

#include<iostream>
#include<queue>
#include<string>
using namespace std;
int main()
{
	priority_queue<int>q;
	string s;
	s="asd";
	int x;
	while(s!="end")
	{
		cin>>s;
		if(s=="insert")
		{
			cin>>x;
			q.push(x);
		}
		else if(s=="extract")
		{
			cout<<q.top()<<endl;
			q.pop();
		}
	}
}

2.ST 表 && RMQ 问题

        认真读题发现题目要求查询数据特定区间内的最大值,按照常规思路,可以使用冒泡排序等方法完成,但仔细一看本题的数据量很大访问次数多,如果每一次访问都进行一次排序那么时间上一定会超时。

ST表

此时我们自然的就进入了一个特殊的做法,STL表

它是解决RMQ问题(区间最值问题)的一个非常重要的方法

可以做到O(nlogn)的预处理时间复杂度,O(1)查询最值

算法简析

核心思想:倍增

原理:把给定区间分成长度是2的幂次的小区间,先预处理出它们中的最小值是多少,然后用一种类似二分的思想由小区间到大区间比较两个区间的最小值

我们用max[i][j]表示,从i位置开始的2^{j}个数中的最大值,例如max[i][1]表示的是i位置i+1位置中两个数的最大值

起始条件:f[i][0]=a[i];(a[i]为给定数列)

(根据上面的定义,f[i][0]代表[i,i]区间最小值(即a[i]:显然区间[i,i]里只有它一个数))。

可以总结,在f[i][j]中,i为区间左端点,j决定f[i][j]包括的比较过的元素个数为2^{j},右端点下标为i+2^{j}-1.

先来模拟一下st表的生成(以便理解)

给定一个数组 2 8 7 10 2

j的最大值为2,n=5;

f[1][0]=2   f[2][0]=8   f[3][0]=7   f[4][0]=5   f[5][0]=1

当j=1时,i的范围为(1-4)

f[1][1]=max(f[1][0],f[2][0])=4;[1,2]

f[2][1]=4

f[3][1]=5

f[4][1]=5

当j=2时, i = 1 to n-bit[j]+1=2

f[1][2]=max(f[1][1],f[3][1])=5;

f[2][2]=5

模拟之后倍增的思路是不是就很明晰啦,那此时又出现了一个问题,我们不能保证右边界r就是=i+2^{x}-1(x为任意值),那么这时候该怎么办呢

由于我们不能保证r就=l+2^x-1(x任意),显然我们应该让以l为起点的st表数据覆盖[l,r]中数足够多,

规定在f[i][j]中,i=l,j=p,len=r-l+1(区间长度);

找最大的p应满足2^p <= len(以l为起点的st表数据覆盖[l,r]中数足够多)

则p=int(log(len)/log(2))(再次提醒:向上取整会越界);

example:对于区间[4,8],p=2,分到[4,4+2^2-1],即[4,7];

因为f[l]p中最大的p满足l+pow(2,p)-1<=r

故查询时分的查询区间[l,r]的右区间的左端点x=r+1-pow(2,p)

如何求得这个x:

对于f[x][r],应有:[x][x+pow(2,p)-1]<=>[x][r]

所以, x+pow(2,p)-1=r

移项,得:x=r+1-pow(2,p)

显然,x>=l (l+pow(2,p)-1<=r,x+pow(2,p)-1=r,故x>=l)

综上[l,r]的最大值 = max( f[l][p] , f[x][R] );

代码

#include <bits/stdc++.h>
using namespace std; 
const int N=1e6+10; 
int smax[N][21];
inline int read()
{
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int f(int l,int r)
{
	int k=log2(r-l+1); 
    return max(smax[l][k],smax[r-(1<<k)+1][k]);
} 
int main()
{ 
	int n,m; 
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{ 
		smax[i][0]=read();
    } 
	for(int j=1;j<=21;j++){ 
		for(int i=1;i+(1<<j)-1<=n;i++){ 
            smax[i][j]=max(smax[i][j-1],smax[i+(1<<(j-1))][j-1]); 
        } 
    } 
	for(int i=1;i<=m;i++)
	{ 
		int l=read(),r=read(); 
		printf("%d\n",f(l,r));
    } 
	return 0; 
}

3.合并果子

来啦来啦,要求合并果子时消耗的体力最少,显然我们只要移动n-1次就可以完全合并,那其实我们只要每次移动的都是当前状态下最轻的一堆就可以实现条件啦,那么此时问题就变为了如何动态的判断每个状态下最小堆,堆的合并和原有堆的删除啦

自然的我们可以想到优先队列小根堆的方式

代码思路:先读入每一个堆储存于小根堆优先数列中\rightarrow用top()找出最小堆a\rightarrow删除最小堆\rightarrow找出此时最小堆b,a和b合并变为一个新堆,记录消耗的体力\rightarrow插入这个新堆\rightarrow如此往复得到只剩一个堆的状态\rightarrow输出总和

#include <bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> >q;
int main()
{
	int n,x,ans;
	cin>>n;
	int a[1000];
	for(int i=1;i<=n;i++)
	{
		cin>>x;
		q.push(x);
	}
	while(q.size()>=2)
	{
		int a=q.top();
		q.pop();
		int b=q.top();
		q.pop();
		ans+=a+b;
		q.push(a+b);
	}
	cout<<ans<<endl;
	return 0;
}

4.约瑟夫问题

此题我们可以将所以人编入一个队列,采用将对首的人不断换到队尾同时采用计数的方式来统计报数,当报道特定数m时输出队首再删除队首,直到队列为空即可,代码如下

#include <bits/stdc++.h>
using namespace std;
int main()
{
	int n,m,num=1;
	cin>>n>>m;
	queue<int> q;
	for(int i=1;i<=n;i++)q.push(i);
	while(!q.empty())
	{
		if(num==m)
		{
			cout<<q.front()<<" ";
			q.pop();
			num=1;
		}
		else
		{
			num++;
			q.push(q.front());
			q.pop();
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值