2018百度之星程序设计大赛(资格赛) 子串查询 HDU6345 解题思路

题目链接 HDU 6345 子串查询

1、题目分析

  本题只要看懂了题意其实还是不难的,题目意思是要求出给定区间中最小子串的个数,所以1、找到最小子串 2、求出最小子串的个数
  根据题意,其实最小子串就是给定区间中字典序最小的单个字母,明白了这点,那么本题的就是求解,给定区间中字典序最小的单个字母出现的次数


2、细节思路

  根据题目,数据的数量级为 1 0 5 10^5 105,暴力查询求解肯定会超时,不难想到,其实这个字典序最小字母次数是满足区间加法的,假设给定两个相连区间 [ a , b ] [a,b] [a,b] [ b , c ] [b,c] [b,c],两个区间中最小字母分别为 x 1 , x 2 x1,x2 x1,x2,出现次数分别为 t 1 , t 2 t1,t2 t1,t2,那么两个区间合并后的区间为 [ a , c ] [a,c] [a,c],合并之后区间的最小字母和出现次数分别记为 x 3 , t 3 x3,t3 x3,t3。不难得到,有以下情况:

条件结论
x 1 = x 2 x1=x2 x1=x2 x 3 = x 1 = x 1 x3=x1=x1 x3=x1=x1 t 3 = t 1 + t 2 t3=t1+t2 t3=t1+t2
x 1 > x 2 x1>x2 x1>x2 x 3 = x 2 x3=x2 x3=x2 $ t3=t2$
x 1 &lt; x 2 x1&lt;x2 x1<x2 x 3 = x 1 x3=x1 x3=x1 t 3 = t 1 t3=t1 t3=t1

满足区间加法的问题都能用线段树来解决,所以此题的关键在于线段树的编写。

3、算法设计

主要是按照上面的表格重写了线段树的 + , = , &gt; , &lt; +,=,&gt;,&lt; +,=,>,<等运算符,如下所示

//注意 !!!重载运算符 + 不要改变本身对象
struct Sum {
	int number;
	char ch;
	Sum() {
		ch = 'Z'+1;
		number = 0;
	}
	Sum operator +(const Sum &rhs) {
		Sum temp;
		temp.number = number;
		temp.ch = ch;
		
		if(temp.ch==rhs.ch)
		temp.number= number+rhs.number;
	   else if (temp.ch > rhs.ch)
	   {
		   temp.number = rhs.number;
		   temp.ch = rhs.ch;
	   }
		return temp;
	}
	Sum& operator =(const Sum &rhs) {
		ch = rhs.ch;
		number = rhs.number;
		return *this;
	}
	bool operator >(const Sum &rhs) {
		return ch > rhs.ch;
	}
	bool operator <(const Sum &rhs)
	{
		return ch < rhs.ch;
	}
	bool operator == (const Sum &rhs)
	{
		return ch == rhs.ch;
	}
};

这里每一个sum有两个属性,ch表示该区间的最小字母,number代表最小字母出现次数。

4、程序代码

其他部分的代码和普通的线段树没什么区别,全部代码如下:

#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#define  mem(a,b) memset(a,b,sizeof(a))
#define  NUM_OF_CHAR  26
using namespace std;
const int maxn = 100000 + 1000;
char A[maxn];
int T;
int length,q;

//注意 !!!重载运算符 + 不要改变本身对象
struct Sum {
	int number;
	char ch;
	Sum() {
		ch = 'Z'+1;
		number = 0;
	}
	Sum operator +(const Sum &rhs) {
		
		Sum temp;
		temp.number = number;
		temp.ch = ch;
		
		if(temp.ch==rhs.ch)
		temp.number= number+rhs.number;
	   else if (temp.ch > rhs.ch)
	   {
		   temp.number = rhs.number;
		   temp.ch = rhs.ch;
	   }

		return temp;
	}
	Sum& operator =(const Sum &rhs) {
		ch = rhs.ch;
		number = rhs.number;
		return *this;
	}

	bool operator >(const Sum &rhs) {
		return ch > rhs.ch;
	}
	bool operator <(const Sum &rhs)
	{
		return ch < rhs.ch;
	}
	bool operator == (const Sum &rhs)
	{
		return ch == rhs.ch;
	}
};


Sum sum[maxn << 2];

//更新节点的函数
inline void PushUp(int rt)
{
	int left = rt << 1;
	int right = rt << 1 | 1;
	//每个字母的个数分别为 左子树和右子树每个字母的个数之和
	sum[rt] = sum[left] + sum[right];

	//printf("sum[%d] = sum[%d] + sum[%d]\n", rt, left, right);

}

//建立线段树
void Build(int l, int r, int rt)
{
	//printf("Build(l = %d ,r = %d, rt = %d)\n", l, r, rt);
	
	//如果到了叶子节点那么保存该节点的值
	if (l == r)
	{	
		//该字母出现一次
		sum[rt].number = 1;
		sum[rt].ch = A[l];
		//printf("到达叶子节点rt = %d  sum[%d].number = %d sum[%d].ch = %c\n", rt, rt, sum[rt].number, rt, sum[rt].ch);
		return;
	}
	//计算中点
	int m = (l + r) >> 1;

	//递归建立左右子树
	Build(l, m, rt << 1);
	Build(m + 1, r, rt << 1 | 1);
	//左右子树建立完毕之后 建立本节点

	PushUp(rt);
}



//查询函数,查找在(L,R)中的每个字母的和
Sum Query(int L, int R, int l, int r, int st)
{
	//printf("L = %d , R= %d, l= %d ,r = %d , st =%d \n", L, R, l, r, st);
	//如果范围在 (L,R)之中,那么直接返回值
	if (L <= l&&R >= r)
	{
		//printf("return sum[%d]\n", st);
		//for (int i = 0; i < 5; i++)
		//	printf("sum[%d].ch[%d] = %d  ", st, i, sum[st].ch[i]);
		//printf("\n");
		return sum[st];
	}

	Sum ans;
	//mem(&ans, 0);
	//printf(" 递归计算\n");
	//for (int i = 0; i < 5; i++)
	//	printf("初始化ans.ch[%d] = %d  ", i, ans.ch[i]);
	//printf("\n");

	int m = (l + r) >> 1;
	//查找与子区间是否存在并集,如果不存在则不用查找
	if (m >= L) ans = ans + Query(L, R, l, m, st << 1);
	if (m < R) ans = ans + Query(L, R, m + 1, r, st << 1 | 1);

	//for (int i = 0; i < 5; i++)
	//	printf("ans.ch[%d] = %d  ",i, ans.ch[i]);
	//printf("\n");
	//统计完毕返回
	return ans;

}

void solve()
{
	int left_bound;
	int right_bound;
	Sum my_ans;
	while (q--)
	{
		scanf("%d%d", &left_bound, &right_bound);

		//printf("left_bound = %d,right_bound = %d \n", left_bound, right_bound);
		my_ans = Query(left_bound, right_bound, 1, length, 1);
			
		//for (int i = 0; i < 5; i++)
		//	printf("my_ans.ch[%d] = %d   ", i, my_ans.ch[i]);
		//printf("\n");

		printf("%d\n", my_ans.number);
	}
}

int main()
{
	int kcase = 1;
	freopen("C:\\Users\\lenovo\\Desktop\\test\\in.txt", "r", stdin);
	freopen("C:\\Users\\lenovo\\Desktop\\test\\out.txt", "w", stdout);
	scanf("%d", &T);
	//mem(sum, 0);
	while (T--)
	{
		scanf("%d%d", &length, &q);
		scanf("%s", A+1);

		Build(1, length, 1);

		//for (int i = 1; i < 6; i++)
		//	printf("sum[%d].ch = %c sum[%d].number = %d  ", i, sum[i].ch, i, sum[i].number);
		//printf("\n");
		printf("Case #%d:\n", kcase++);
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值