ST表入门 HDU3183 +鸽巢原理

本文介绍了ST表算法的基础知识,主要用于解决区间最值查询问题(RMQ)。通过理解核心查询代码,阐述了如何在查询过程中避免漏掉区间元素。并分析了HDU3183题目的解题思路,提供了两种解决方案:贪心算法和结合抽屉原理的RMQ应用。特别强调了在实现中比较函数应设置为小于等于,以确保找到所有可能的最小值。
摘要由CSDN通过智能技术生成
  1. 入门ST表算法
    解决问题:RMQ
    教程:https://www.cnblogs.com/qq965921539/p/9608980.html
    关键理解之处:查询之时的核心代码:
int search(int l,int r)
{
    int k=(int)(log((double)(r-l+1))/log(2.0));//比如说log(4)=2,log(5)=2,log(6)=2,log(7)=2,log(8)=3,log(9)=3……. 那么我们要查询x到y的最小值。 设len=y-x+1,t=log(len) 根据上面的定理:2^t>len/2
    return min(st[l][k],st[r-(1<<k)+1][k]);//x到y的最小值可以表示为min(从x往后2^t的最小值,从y往前2^t的最小值
}

此处查询举例,如果查找长度为7的区间,则查找区间前4位的子区间和后四位的子区间,两个子区间重叠了一个位,而查找长度位5的区间,也是查找区间前4位的子区间和后四位的子区间,虽然还是重叠了,但是总比漏了好,对结果并不会影响。其原理其实加入是查找一个区间长度为N的区间,其会取长度为2^k的长度,这个长度是刚好比N大一点的2的幂次数的一半,所以一定也比N/2大,故取子区间时子区间只会重叠,而不会漏掉母区间中间的元素

2.HDU3183
参考blog:
https://blog.csdn.net/bjfu170203101/article/details/87950240
如同以上博客所说,有贪心的思路:
贪心思路代码:

#include<bits/stdc++.h>
#define LL long long
#define ms0(x) memset(x,0,sizeof(x))
#define ms-1(x) memset(x,-1,sizeof(x))
bool vis[1003]; 
using namespace std;
int main()
{
 	list<int> li;
 	string str;
 	while(cin>>str)
 	{
 		li.clear();
 		int m;
		cin>>m;
		for(int j=0;j<str.size();j++)
			li.push_back(str[j]-'0');
		list<int>::iterator it=li.begin();
		it++;
		for(;m>0&&it!=li.end();it++)
		{
			if(it==li.begin())
				continue;
			list<int> ::iterator l_it=--it;
			it++;
			if((*l_it)<=(*it))
			{
				continue;
			}	
			else
			{
				m--;
				li.erase(l_it);
				if(it!=li.begin());
				{
					it--;
				}
			}
		}
		while(m--)
		{
			it=li.end();
			it--;
			li.erase(it);	
		}
		it=li.begin();
		while(*it==0&&it!=li.end())
		{
			li.erase(it);
			it=li.begin();
		}	
		it=li.begin();
		while(it!=li.end())
		{
			cout<<*it;
			it++;
		}
		if(li.empty())
			cout<<"0"<<endl;
		else
			cout<<endl;
	}
}

贪心构造方法都大同小异,感觉有list真的很麻烦,不如直接用string的迭代器,就是可能会慢一点。

第二种思路:抽屉原理+RMQ
这个思路才是亮点,前者贪心的角度,是假设每次只删除一个数,从左到右扫数列,角度是只从一个数。而第二种思路的角度是从区间上看的,从抽屉原理可知,从N个数删掉M个数,并且留下的数顺序不变,且最大,则留下的数中的第一位一定在前M+1位里面,即第0位到第N-M+1,第二位则在第一位的位置之后到第N-M+2位,以此类推到最后。
两个思路的本质都在于,留下的数中每一位的优先级不同,为使得到的数最小,最前位的优先级最大,尽可能让最前位最小,即在最前位能取的范围中,取最小的那个。

题目坑点:比较下表的函数要写成小于等于,即相同大小元素尽可能取左边的那个,因为相同都最小的话,尽可能都留着,否者会漏掉最前面的较小值,而样例中的第三个数据会错。
代码:

#include<bits/stdc++.h>
#define LL long long
#define ms0(x) memset(x,0,sizeof(x))
#define ms-1(x) memset(x,-1,sizeof(x))
const int maxn = 1e3+3;
using namespace std;

//---------the template of Sparse Table---------//
//st表的主体是一个二维数组st[i][j],表示需要查询的数组的从下标i到下标i+2^j - 1的最值
//此处的min_pos是比较两个下表对应元素的大小,返回的小于等于的那个元素的下标,直接比较最小值的话只需直接用min,不用记录下标。
int a[maxn];//the array where u input your data
int st[maxn][20];//Sparse Table
int min_pos(int tmp1,int tmp2)
{
    if(a[tmp1]<=a[tmp2])
    {
        return tmp1;
    }
    else
    {
        return tmp2;
    }
}
void init(int n)//initialize the table
{
    for(int i=0; i<n; i++)
        st[i][0]=i;

    for(int j=1; (1<<j)<=n; j++)//j means power
    {
        for(int i=0; i+(1<<j)-1<n; i++)
            st[i][j]=min_pos(st[i][j-1],st[i+(1<<(j-1))][j-1]);//transformation equation
    }
}
int search(int l,int r)
{
    int k=(int)(log((double)(r-l+1))/log(2.0));//比如说log(4)=2,log(5)=2,log(6)=2,log(7)=2,log(8)=3,log(9)=3……. 那么我们要查询x到y的最小值。 设len=y-x+1,t=log(len) 根据上面的定理:2^t>len/2
    return min_pos(st[l][k],st[r-(1<<k)+1][k]);//x到y的最小值可以表示为min(从x往后2^t的最小值,从y往前2^t的最小值
}
//---------the template of Sparse Table---------//
int ans[maxn];
int main()
{
    ios::sync_with_stdio(false);
    string str;
    while(cin>>str)
    {
        int n=str.size();
        for(int j=0;j<str.size();j++)
            a[j]=str[j]-'0';
        init(n);
        int m;int cnt=0;
        cin>>m;int pos=0;
        for(int j=m;j<n;j++)//鸽巢原理
        {
            pos=search(pos,j);
            ans[cnt++]=a[pos];
            pos++;
        }
        bool flag=0;
        for(int j=0;j<cnt;j++)
        {
            if(ans[j]!=0)
            {
                flag=1;
            }
            if(flag==1)
                cout<<ans[j];
        }
        if(flag==0||cnt==0)
            cout<<"0";
        cout<<endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值