2018百度之星程式設計大賽(資格賽) 調查問卷 HDU6344 解題思路
阿新 • • 發佈:2018-12-19
題目連結 HDU 6345 子串查詢
1、題目分析
本題只要看懂了題意其實還是不難的,題目意思是要求出給定區間中最小子串的個數,所以1、找到最小子串 2、求出最小子串的個數 根據題意,其實最小子串就是給定區間中字典序最小的單個字母,明白了這點,那麼本題的就是求解,給定區間中字典序最小的單個字母出現的次數
2、細節思路
根據題目,資料的數量級為,暴力查詢求解肯定會超時,不難想到,其實這個字典序最小字母次數是滿足區間加法的,假設給定兩個相連區間和,兩個區間中最小字母分別為,出現次數分別為,那麼兩個區間合併後的區間為,合併之後區間的最小字母和出現次數分別記為。不難得到,有以下情況:
條件 | 結論 |
---|---|
$ t3=t2$ | |
滿足區間加法的問題都能用線段樹來解決,所以此題的關鍵在於線段樹的編寫。
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;
}