1. 程式人生 > >2018百度之星程式設計大賽(資格賽) 調查問卷 HDU6344 解題思路

2018百度之星程式設計大賽(資格賽) 調查問卷 HDU6344 解題思路

題目連結 HDU 6345 子串查詢

1、題目分析

  本題只要看懂了題意其實還是不難的,題目意思是要求出給定區間中最小子串的個數,所以1、找到最小子串 2、求出最小子串的個數   根據題意,其實最小子串就是給定區間中字典序最小的單個字母,明白了這點,那麼本題的就是求解,給定區間中字典序最小的單個字母出現的次數

2、細節思路

  根據題目,資料的數量級為10510^5,暴力查詢求解肯定會超時,不難想到,其實這個字典序最小字母次數是滿足區間加法的,假設給定兩個相連區間[a,b][a,b][b,c][b,c],兩個區間中最小字母分別為x1,x2x1,x2,出現次數分別為t

1,t2t1,t2,那麼兩個區間合併後的區間為[a,c][a,c],合併之後區間的最小字母和出現次數分別記為x3,t3x3,t3。不難得到,有以下情況:

條件 結論
x1=x2x1=x2 x3=x1=x1x3=x1=x1t3=t1+t2t3=t1+t2
x1>x2x1>x2 x3=x2x3=x2 $ t3=t2$
x1<x2x1<x2 x3=x1x3=x1t3=t1t3=t1

滿足區間加法的問題都能用線段樹來解決,所以此題的關鍵在於線段樹的編寫。

3、演算法設計

主要是按照上面的表格重寫了線段樹的 +,=,>,<+,=,>,<等運算子,如下所示

//注意 !!!過載運算子 + 不要改變本身物件
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;
}