#1032 : 最长回文子串

1.使用manacher算法进行检测,时间复杂度为O(n)

2.其中需要对字符串进行预处理,头部加上$,然后间隔加上#,如123变为$#1#2#3#

3.需要额外添加变量id,即最长回文的中心,mx,即最长回文的右边界

4.其中当mx>i,我们有定理p[i]>=min(p[j],mx-i),其中j为i关于id的对称点,即j=2*id-i;即直接从p[i]进行检测

5.当mx<i时,无法进行更多的预测,需要从p[i]=1开始检测

6.检测完以当前字符串为中心的回文后,如果p[i]+i大于最长回文的右边界,则进行更新mx=p[i]=i,id=i;

时间限制:1000ms
单点时限:1000ms
内存限制:64MB

描述

小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”

小Ho奇怪的问道:“什么叫做最长回文子串呢?”

小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”

小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?

小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”

提示一 提示二 提示三 提示四

样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3

AC代码:
[c language=”++”]
/*
题目:
1.使用manacher算法进行检测,时间复杂度为O(n)
2.其中需要对字符串进行预处理,头部加上$,然后间隔加上#,如123变为$#1#2#3#
3.需要额外添加变量id,即最长回文的中心,mx,即最长回文的右边界
4.其中当mx>i,我们有定理p[i]>=min(p[j],mx-i),其中j为i关于id的对称点,即j=2*id-i;即直接从p[i]进行检测
5.当mx<i时,无法进行更多的预测,需要从p[i]=1开始检测
6.检测完以当前字符串为中心的回文后,如果p[i]+i大于最长回文的右边界,则进行更新mx=p[i]=i,id=i;
*/

#include<string>
#include <iomanip>
#include<fstream>
#include<set>
#include<queue>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
//#include <sstream>
//#include "func.h"
//#include <list>
#include<stdio.h>
#include<iostream>
#include<string>
#include<memory.h>
#include<limits.h>
//#include<stack>
#include<vector>
#include <algorithm>
using namespace std;

int manacher(string str)
{
vector<int > p(str.size(), 0);
int id = 1;//初始化开始检测回文的中心为1
int mx = 0;//初始化最大的边界值为0
for (int i = 1; i < str.size(); i++)
{
//如果边界值mx大于i,那么p[i] = min(p[2 * id – i], mx – i);
if (mx > i)
p[i] = min(p[2 * id – i], mx – i);
//否则,无法对p[i]进行预测,令p[i]=1
else
p[i] = 1;
//检测回文
while (str[i + p[i]] == str[i – p[i]])
p[i]++;
//如果大于最大边界,则进行更新
if (p[i] + i > mx)
{
mx = p[i] + i;
id = i;
}
}
int maxP=INT_MIN;
for (int i = 0; i < p.size(); i++)
maxP = max(p[i], maxP);
return maxP – 1;
}
/*
函数名 :main
函数功能:主函数
*/
int main(void)
{
int n;
scanf("%d", &n);
while (n–)
{
string str;
cin >> str;
string newStr = "$#";
for (int i = 0; i < str.size(); i++)
{
newStr += str[i] ;
newStr += ‘#’;
}
cout << manacher(newStr) << endl;
}
return 0;
}

[/c]

315. Count of Smaller Numbers After Self(Hard)

题目要求出所有元素的其右边比它小的元素个数。

我的解法:

1.先对数组进行排序,然后采用树状数组记录比当前元素出现次数少的元素的个数。如5,4,7,2,排序后为2,4,5,7,那么树状数组记录了0(比2小的元素个数),1(比4小的元素个数),2(比5小的元素个数),3(比7小的元素个数)。

2.查询的时候查询比元素nums[i]小的元素出现次数总和,查询当前元素时,对当前元素的出现次数进行-1操作(同时会更新树状数组),表示当前元素出现在后面元素的前面,后面的元素统计时不应该把这个元素的数量计算进去。

3.按照nums的次序进行查询,这样可以确保已经查询过的元素(即在当前元素左边的元素)在树状数组里面的出现次数也相应减少了,所以可以放心查询比nums[i]元素小的元素个数。

4.排序使用了set,时间复杂度为O(nlogn),后面树状数组查询操作时间复杂度为O(nlogn)。

You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

Example:

Given nums = [5, 2, 6, 1]

To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.

Return the array [2, 1, 1, 0].

AC代码:

[c language=”++”]
class Solution {
public:
vector<int> tree;
//树状数组函数:
int lowbit(int x)
{
return (x&-x);
}

void add(int x, int val)
{
for (int i = x; i<tree.size(); i += lowbit(i))
tree[i] += val;
}

int get(int x)
{
int sum = 0;
for (int i = x; i; i -= lowbit(i))
sum += tree[i];
return sum;
}

vector<int> countSmaller(vector<int>& nums) {

set<int> sortNumsSet;
map<int, int> times;

//对nums进行排序和统计
for (int i = 0; i<nums.size(); i++)
{
times[nums[i]]++;
sortNumsSet.insert(nums[i]);
}
//建立树状数组
tree = vector<int>(sortNumsSet.size() + 1, 0);

map<int, int> pos;
vector<int> sortNums(sortNumsSet.size(), 0);
set<int>::iterator ite = sortNumsSet.begin();
//复制到新的数组中,并且记录元素的位置。
for (int i = 0; i<sortNums.size(); i++)
{
sortNums[i] = *ite;
pos[sortNums[i]] = i + 1;
ite++;
add(i + 1, times[sortNums[i]]);
}

vector<int> ans(nums.size(), 0);
for (int i = 0; i<nums.size(); i++)
{
//当前元素的出现次数-1
add(pos[nums[i]], -1);
//获取nums[i]前面的元素总数
ans[i] = get(pos[nums[i]] – 1);
//cout<<ans[i]<<‘ ‘;
}

return ans;

}
};
[/c]

307. Range Sum Query – Mutable(Medium)

该题目是一个区间求和及更新问题,使用树状数组(Binary Indexed Tree)求解。

相关文章:1057. Stack (30)

需要注意的是:

1.采用两个数组,一个是原始数据数组,一个是树状数组;

2.树状数组的下标需要从1开始,不然在使用(x&-x)时恒为0

3.更新完树状数组后,原数组也要进行更新(卡在这里);

 

Given an integer array nums, find the sum of the elements between indices i and j (ij), inclusive.

The update(i, val) function modifies nums by updating the element at index i to val.

Example:

Given nums = [1, 3, 5]

sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8

Note:

  1. The array is only modifiable by the update function.
  2. You may assume the number of calls to update and sumRange function is distributed evenly.

AC代码:

[c language=”++”]
class NumArray {
public:
vector<int> num;
vector<int> tree;

NumArray(vector<int> &nums) {
//初始化两个数组,一个是num,用来存储原来的数组
num = nums;
//一个是树状数组,存储和信息
tree = vector<int>(nums.size() + 1, 0);
for (int i = 0; i < nums.size(); i++)
{//采用add操作进行更新
add(i + 1, nums[i]);
}
}

void update(int i, int val) {
//求出差值,进行add
int diff = val – num[i];
add(i + 1, diff);
//由于update操作设计到val-num[i],所以除了更新树状数组,原数组也需要更新
num[i] = val;
}

int sumRange(int i, int j) {
//求出1到j+1的和,求出1到i的和,然后进行相减
return get(j+1) – get(i);
}
//x=0时返回0,所以x必须>=1
int lowbit(int x)
{
return (x&-x);
}
void add(int x, int value)
{
for (int i = x; i < tree.size(); i += lowbit(i))
tree[i] += value;
}

//get操作是得到1到x的和
int get(int x)
{
int sum = 0;
for (int i = x; i; i -= lowbit(i))
sum += tree[i];
return sum;
}
};

// Your NumArray object will be instantiated and called as such:
// NumArray numArray(nums);
// numArray.sumRange(0, 1);
// numArray.update(1, 10);
// numArray.sumRange(1, 2);
[/c]

#1077 : RMQ问题再临-线段树

1.这道题目使用线段树求解。

2.其中,线段树有三个操作,初始化、更新和查询。

3.初始化操作是把数组初始化为最大值,这时尚未进行填充。数组扩展到了2的n次方大小。

4.更新操作是把值更新到叶子节点及叶子节点的祖先上,这个时候才是真正意义的初始化数组值。

5.查询使用递归实现。

6.该题比较奇怪,有时候能够ac,有时候会tle,同样的代码,但是没有想到更好的优化办法。

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

上回说到:小Hi给小Ho出了这样一道问题:假设整个货架上从左到右摆放了N种商品,并且依次标号为1到N,每次小Hi都给出一段区间[L, R],小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,并且告诉小Hi这个商品的重量。但是在这个过程中,可能会因为其他人的各种行为,对某些位置上的商品的重量产生改变(如更换了其他种类的商品)。

小Ho提出了两种非常简单的方法,但是都不能完美的解决。那么这一次,面对更大的数据规模,小Ho将如何是好呢?

提示:其实只是比ST少计算了一些区间而已

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量weight_i。

每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数与商品的重量被更改的次数之和。

每组测试数据的第N+4~N+Q+3行,每行分别描述一次操作,每行的开头均为一个属于0或1的数字,分别表示该行描述一个询问和描述一次商品的重量的更改两种情况。对于第N+i+3行,如果该行描述一个询问,则接下来为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri];如果该行描述一次商品的重量的更改,则接下来为两个整数Pi,Wi,表示位置编号为Pi的商品的重量变更为Wi

对于100%的数据,满足N<=10^6,Q<=10^6, 1<=Li<=Ri<=N,1<=Pi<=N, 0<weight_i, Wi<=10^4。

输出

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。

样例输入
10
3655 5246 8991 5933 7474 7603 6098 6654 2414 884 
6
0 4 9
0 2 10
1 4 7009
0 5 6
1 3 7949
1 3 1227
样例输出
2414
884
7474

[c language=”++”]
/*
题目:
实则为RMQ问题。
主要使用线段树求解。
*/

//#include<string>
//#include <iomanip>
#include<fstream>
//#include<set>
//#include<queue>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
//#include <sstream>
//#include "func.h"
//#include <list>
#include<stdio.h>
#include<iostream>
#include<string>
#include<memory.h>
#include<limits.h>
//#include<stack>
#include<vector>
#include <algorithm>
using namespace std;

const int MAX_N = 1 << 20;
int SegTree[2 * MAX_N – 1];
int n;
void init(int size)
{
n = 1;
//把线段树的大小扩充到2的n次方
while (n < size) n *= 2;
//初始化为最大值
for (int i = 0; i < 2 * n – 1; i++)
SegTree[i] = INT_MAX;
}

void update(int k, int val)
{
//前面n-1个节点用来存储非叶子节点的线段树节点
//最后的n个节点是用来存储数组
k += n – 1;
SegTree[k] = val;
while (k > 0)
{
//找到父节点
k = (k – 1) / 2;
//取左右儿子的最大值
SegTree[k] = min(SegTree[k * 2 + 1], SegTree[k * 2 + 2]);
}
}

int query(int a, int b, int k,int l,int r)
{

//如果[a,b)与[l,r)不相交
if (r <= a || b <= l) return INT_MAX;

//如果[a,b)包含[l,r),则返回当前节点的值
if (a <= l && r <= b||k>n) return SegTree[k];
else
{//把区间分成两部分进行查询
int vl = query(a, b, k * 2 + 1, l, (l + r) / 2);
int vr = query(a, b, k * 2 + 2, (l + r) / 2, r);
return min(vl, vr);
}

}

/*
函数名 :main
函数功能:主函数
*/
int main(void)
{
int sum;
scanf("%d", &sum);
init(sum);
for (int i = 0; i < sum; i++)
{
int tmp;
scanf("%d", &tmp);
//初始化的时候使用update操作
update(i, tmp);
}
int queryN;
scanf("%d", &queryN);
while (queryN–)
{
int operation, a, b;
scanf("%d %d %d", &operation, &a, &b);
if (operation == 0)
{
//因为查询求解的是[a,b)区间,需要注意
printf("%d\n", query(a-1, b, 0, 0, n));
}
else
{
update(a – 1, b);
}
}

return 0;
}

[/c]

#1074 : 字体设计

题目:
实则为RMQ问题。

主要求出锚点,锚点即为包含区间的两端,包含区间里面的数统一大于起点,且小于终点;或者统一小于起点,且大于重点。

使用ST表进行求解,其中我们需要求出两个ST表,一个用来存储最大值的,一个用来存储最小值的。

我们通过不断递归搜索区间[l,r],找出区间中的最大值maxPos和最小值minPos的位置,然后划分为三个区间:
如果maxPos>minPos,则三个区间为:
[l,minPos],[minPos,maxPos],[maxPos,r]

如果maxPos<minPos,则三个区间为:
[l,maxPos],[maxPos,minPos],[minPos,r]

然后挑选出锚点。

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

你正在协助某人开发某种新的 Linux 下的中文字体。

现在需要给上图里的黄点做「位置锚固」,然而有些黄点的位置可以由「插值(IUP)」确定,这样就可以减少锚固的数量。

例如,在上图中,#46 #49 #52 #53 的位置可以由 #42 和 #57 插值得到, 我就不需要去锚固它们,只需要固定 #42 和 #57 就可以了。 可以给这个字减少至少 12 字节 ……

抽象一下问题,现在给出输入数组 a

定义 ax 可以被 alar 插值得到为:

存在 l < x < r

使得 al ≤ ax ≤ ar 或者 al ≥ ax ≥ ar

求最少的「锚固」元素的数目,使得非锚固元素都可以由其左右最靠近它的锚固元素插值得到。并输出锚固元素的下标。

输入

第一行输入一个数 n,表示数组的大小(1 ≤ n ≤ 105)。 接下来的一行,输入一行 n 个整数 a1, a2, …, an,表示数组中的元素(1 ≤ ai ≤ 109)。所有 ai 互不相同。

输出

输出的第一行包含一个整数 ans,表示锚固元素的数目。 接下来一行包含 ans 个递增的整数,表示锚固元素的下标。

提示

额外的样例数据:

样例输入 样例输出
7
1 2 3 10 5 6 4
3
1 4 7
样例输入
8
3 4 2 1 8 5 7 6
样例输出
7
1 2 4 5 6 7 8

AC代码:
[c language=”++”]
/*
题目:
实则为RMQ问题。

主要求出锚点,锚点即为包含区间的两端,包含区间里面的数统一大于起点,且小于终点;或者统一小于起点,且大于重点。

使用ST表进行求解,其中我们需要求出两个ST表,一个用来存储最大值的,一个用来存储最小值的。

我们通过不断递归搜索区间[l,r],找出区间中的最大值maxPos和最小值minPos的位置,然后划分为三个区间:
如果maxPos>minPos,则三个区间为:
[l,minPos],[minPos,maxPos],[maxPos,r]

如果maxPos<minPos,则三个区间为:
[l,maxPos],[maxPos,minPos],[minPos,r]

然后挑选出锚点
*/

//#include<string>
//#include <iomanip>
#include<fstream>
//#include<set>
//#include<queue>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
//#include <sstream>
//#include "func.h"
//#include <list>
#include<stdio.h>
#include<iostream>
#include<string>
#include<memory.h>
#include<limits.h>
//#include<stack>
#include<vector>
#include <algorithm>
using namespace std;

vector<int> preLog2;
//预先计算log2数组,方便后续处理
void calPreLog2(int n,vector<int>&log2)
{
log2[1] = 0;
for (int i = 2; i <= n; i++)
{
log2[i] = log2[i – 1];
//恰好是2的次方
if ((1 << (log2[i] + 1)) == i)
log2[i]++;
}
}
vector<int> arr;
vector<vector<pair<int, int>>> stMin;
vector<vector<pair<int, int>>> stMax;

//准备st表
void ST_Prepare()
{
for (int i = arr.size() – 1; i >= 0; i–)
{
stMin[i][0] = { arr[i], i };
stMax[i][0] = { arr[i], i };
for (int j = 1; i + (1 << j) – 1 < arr.size(); j++)
{
//求min
if (stMin[i][j – 1].first < stMin[i + (1 << (j – 1))][j – 1].first)
stMin[i][j] = stMin[i][j – 1];
else
stMin[i][j] = stMin[i + (1 << (j – 1))][j – 1];

//求max
if (stMax[i][j – 1].first > stMax[i + (1 << (j – 1))][j – 1].first)
stMax[i][j] = stMax[i][j – 1];
else
stMax[i][j] = stMax[i + (1 << (j – 1))][j – 1];
}
}
}
//查询最小值
int queryMin(int l, int r)
{
int len = r – l + 1;
int k = preLog2[len];
if (stMin[l][k].first < stMin[r – (1 << k) + 1][k].first)
return stMin[l][k].second;
else
return stMin[r – (1 << k) + 1][k].second;
}
//查询最大值
int queryMax(int l, int r)
{
int len = r – l + 1;
int k = preLog2[len];
if (stMax[l][k].first > stMax[r – (1 << k) + 1][k].first)
return stMax[l][k].second;
else
return stMax[r – (1 << k) + 1][k].second;
}
map<int,int> ans;

//递归找出包含区间,这些区间的特点是起点和终点分别是最大值和最小值(或最小值和最大值)
void dfs(int l,int r)
{
//求出最大值最小值的位置
int minPos = queryMin(l, r);
int maxPos = queryMax(l, r);
if (l == r)
{//如果左右相等,则压入答案后直接返回
ans[l]=1;
return;
}
//如果最大值和最小值恰好是区间的两端,则压入答案后直接返回
else if ((l == minPos&&r == maxPos) || (l == maxPos &&r == minPos))
{
ans[l] = 1;
ans[r] = 1;
return;
}

//进行递归
dfs(l, min(minPos, maxPos));
dfs(min(minPos, maxPos), max(minPos, maxPos));
dfs(max(minPos, maxPos), r);
}

/*
函数名 :main
函数功能:主函数
*/
int main(void)
{
int n;
scanf("%d", &n);
arr = vector<int>(n, 0);
for (int i = 0; i < n;i++)
{
scanf("%d", &arr[i]);
}

preLog2 = vector<int>(n+1, 0);
stMin = vector<vector<pair<int, int>>>(n, vector<pair<int, int>>(32, { 0, 0 }));
stMax = vector<vector<pair<int, int>>>(n, vector<pair<int, int>>(32, { 0, 0 }));

//预处理求出log2,log2向下取整
calPreLog2(n, preLog2);
//预处理stTable,包括最大值和最小值
ST_Prepare();
//进行递归搜索
dfs(0, n – 1);

printf("%d\n", ans.size());
for (map<int, int>::iterator ite = ans.begin(); ite != ans.end(); ite++)
{
printf("%d ", ite->first + 1);
}
printf("\n");
return 0;
}

[/c]

#1070 : RMQ问题再临

1.根据题意,进行普通的遍历操作求解。

2.使用ST表的话,更新需要O(nlogn)的时间复杂度。

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

终于,小Hi和小Ho踏上了回国的旅程。在飞机上,望着采购来的特产——小Hi陷入了沉思:还记得在上上周他们去超市的时候,前前后后挑了那么多的东西,都幸运的没有任何其他人(售货员/其他顾客)来打搅他们的采购过程。但是如果发生了这样的事情,他们的采购又会变得如何呢?

于是小Hi便向小Ho提出了这个问题:假设整个货架上从左到右摆放了N种商品,并且依次标号为1到N,每次小Hi都给出一段区间[L, R],小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,并且告诉小Hi这个商品的重量。但是在这个过程中,可能会因为其他人的各种行为,对某些位置上的商品的重量产生改变(如更换了其他种类的商品),面对这样一个问题,小Ho又该如何解决呢?

提示:平衡乃和谐之理

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量weight_i。

每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数与商品的重量被更改的次数之和。

每组测试数据的第N+4~N+Q+3行,每行分别描述一次操作,每行的开头均为一个属于0或1的数字,分别表示该行描述一个询问和描述一次商品的重量的更改两种情况。对于第N+i+3行,如果该行描述一个询问,则接下来为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri];如果该行描述一次商品的重量的更改,则接下来为两个整数Pi,Wi,表示位置编号为Pi的商品的重量变更为Wi

对于100%的数据,满足N<=10^4,Q<=10^4, 1<=Li<=Ri<=N,1<=Pi<=N, 0<weight_i, Wi<=10^4。

输出

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。

样例输入
10
618 5122 1923 8934 2518 6024 5406 1020 8291 2647 
6
0 3 6
1 2 2009
0 2 2
0 2 10
1 1 5284
0 2 5
样例输出
1923
2009
1020
1923

AC代码:
[c language=”++”]
//#include<string>
//#include <iomanip>
#include<fstream>
//#include<set>
//#include<queue>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
//#include <sstream>
//#include "func.h"
//#include <list>
#include<stdio.h>
#include<iostream>
#include<string>
#include<memory.h>
#include<limits.h>
//#include<stack>
#include<vector>
#include <algorithm>
using namespace std;
/*
根据题意,使用普通的遍历操作
*/
/*
函数名 :main
函数功能:主函数
*/
int main(void)
{
int n;
scanf("%d", &n);
vector<int> weight(n, 0);
for (int i = 0; i < n;i++)
{
scanf("%d", &weight[i]);
}

int queryN;
scanf("%d", &queryN);
while (queryN–)
{
int operation;
int a, b;
scanf("%d %d %d",&operation, &a, &b);
//进行修改操作
if (operation == 1)
weight[a – 1] = b;
else
{//进行查询操作
int result = INT_MAX;
for (int i = a – 1; i < b; i++)
result = min(weight[i], result);
printf("%d\n", result);
}
}

return 0;
}

[/c]

#1068 : RMQ-ST算法

这道题目主要是求解RMQ问题,给出某个查询的范围,求出该范围里面的最小值。

注意输入的查询是1到N,需要进行-1操作。

使用了ST算法,ST算法的详细说明为:ST表(Sparse Table)

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

小Hi和小Ho在美国旅行了相当长的一段时间之后,终于准备要回国啦!而在回国之前,他们准备去超市采购一些当地特产——比如汉堡(大雾)之类的回国。

但等到了超市之后,小Hi和小Ho发现者超市拥有的商品种类实在太多了——他们实在看不过来了!于是小Hi决定向小Ho委派一个任务:假设整个货架上从左到右拜访了N种商品,并且依次标号为1到N,每次小Hi都给出一段区间[L, R],小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,并且告诉小Hi这个商品的重量,于是他们就可以毫不费劲的买上一大堆东西了——多么可悲的选择困难症患者。

(虽然说每次给出的区间仍然要小Hi来进行决定——但是小Hi最终机智的选择了使用随机数生成这些区间!但是为什么小Hi不直接使用随机数生成购物清单呢?——问那么多做什么!)

提示一:二分法是宇宙至强之法!(真的么?)

提示二:线段树不也是二分法么?

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量weight_i。

每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数。

每组测试数据的第N+4~N+Q+3行,每行分别描述一个询问,其中第N+i+3行为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri]。

对于100%的数据,满足N<=10^6,Q<=10^6, 1<=Li<=Ri<=N,0<weight_i<=10^4。

输出

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。

样例输入
10
7334
1556
8286
1640
2699
4807
8068
981
4120
2179
5
3 4
2 8
2 4
6 8
7 10
样例输出
1640
981
1556
981
981

AC代码:

[c language=”++”]
//#include<string>
//#include <iomanip>
#include<fstream>
//#include<set>
//#include<queue>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
//#include <sstream>
//#include "func.h"
//#include <list>
#include<stdio.h>
#include<iostream>
#include<string>
#include<memory.h>
#include<limits.h>
//#include<stack>
#include<vector>
#include <algorithm>
using namespace std;

/*
这道题目主要是求解RMQ问题,给出某个查询的范围,求出该范围里面的最小值。

使用了ST算法,ST算法的详细说明为:http://maybi.cn/wp_siukwan/?p=830
*/

//stTable的准备函数
void ST_Prepare(vector<int> a, vector<vector<int>>&stTable)
{
for (int i = a.size()-1; i >=0; i–)
{
//初始化0
stTable[i][0] = a[i];
//使用动态规划计算出stTable
for (int j=1; i + (1<<j) -1 < a.size(); j++)
{
stTable[i][j] = min(stTable[i][j – 1], stTable[i + (1<<(j-1)) ][j – 1]);
}
}
}

int queryMin(int l, int r, vector<vector<int>>&stTable)
{
int len = r – l + 1;
int k = 0;
int tmpLen = len;
//求出合适的指数k
while (tmpLen != 1)
{
k++;
tmpLen = (tmpLen >> 1);
}
return min(stTable[l][k], stTable[r – (1 << k) + 1][k]);
}

/*
函数名 :main
函数功能:主函数
*/
int main(void)
{
int n;
scanf("%d", &n);
vector<int> a(n, 0);
for (int i = 0; i < n;i++)
{
scanf("%d", &a[i]);
}

vector<vector<int>>stTable(n, vector<int>(32, 0));
ST_Prepare(a, stTable);

int queryN;
scanf("%d", &queryN);
while (queryN–)
{
int l, r;
scanf("%d %d", &l, &r);
int answer = queryMin(l-1, r-1, stTable);
printf("%d\n", answer);
}

return 0;
}

[/c]

ST表(Sparse Table)

ST表主要用来求解RMQ(Range Minimum/Maximum Query)问题。

ST表能够解决的问题是:

给定一个数组a[n],动态查询数组a[l]到a[r],即l到r范围内的最大值最小值。下面的讲解主要以最小值为主。

一、预处理

首先,我们预处理出数组stTable[i][j],stTable[i][j]代表从a[i]元素开始(包括a[i])连续的2^j个元素的最小值。

我们需要使用动态规划来求解stTable[i][j],初始化为stTable[i][0]=a[i]。

其中stTable[i][j] = stTable[i][j-1] + stTable[i+2^(j-1)][j-1]。举例如下:

stTable[i][1]=a[i]+a[i+1] = stTable[i][0]+stTable[i+1][0]

stTable[i][2]=a[i]+a[i+1] +a[i+2] +a[i+3] = stTable[i][1]+stTable[i+2][0]

…….

二、求出范围[l,r]中的最小值

由于stTable[i][j]存储的信息是包含2^j个元素的,所以我们需要进行拆分或者量化,如下图:

ST

我们把查询的区间[l,r]分别两个相交区间[l,l+2^j-1]和[r-2^j+1,r],即stTable[l][j]和stTable[r-2^j][j],这两个区间需要会相交,但是不影响我们求这个区间里面的最小值,即min(stTable[l][j],stTable[r-2^j][j])。

在给出区间[l,r]后,我们采用移位操作求出j的值,使得l+2^j-1<=r且l+2^(j+1)-1>r。

下面是一些基本的操作:

[c language=”++”]
//stTable的准备函数
void ST_Prepare(vector<int> a, vector<vector<int>>&stTable)
{
for (int i = a.size()-1; i >=0; i–)
{
//初始化0
stTable[i][0] = a[i];
//使用动态规划计算出stTable
for (int j=1; i + (1<<j) -1 < a.size(); j++)
{
stTable[i][j] = min(stTable[i][j – 1], stTable[i + (1<<(j-1)) ][j – 1]);
}
}
}

int queryMin(int l, int r, vector<vector<int>>&stTable)
{
int len = r – l + 1;
int k = 0;
int tmpLen = len;
//求出合适的指数k
while (tmpLen != 1)
{
k++;
tmpLen = (tmpLen >> 1);
}
return min(stTable[l][k], stTable[r – (1 << k) + 1][k]);
}
[/c]

 

转:hiho一下第76周《Suzhou Adventure》题目分析

题目大意

给定一颗N个节点的树,每个节点有权值W[i],从中选择M个节点,使得这M个节点的权值之和最大。同时满足:

  • 条件一:当一个节点被选择后,它的所有祖先节点也要被选择
  • 条件二:其中有K个节点是必须要选择的

解体思路

本题所考察的算法是树形动态规划。

对于两个附加条件,我们分别进行处理:

条件一:当一个节点被选择后,它的所有祖先节点也要被选择

该条件换一个说法,可以解释为:只有当选择了一个节点后,我们才可以选择它的子节点。

我们首先建立状态f[i][k]f[i][k]表示以i节点为根的子树,在满足条件一的情况下,选择至多k的节点能够得到的最大权值。

则可以写出状态转移情况:

  • 选择i节点:f[i][k]等于w[i]加上所有子节点选择k-1个节点的最大权值
  • 不选择i节点:f[i][k] = 0

不选择i节点很好处理,那么我们如何处理选择i节点时,子节点的情况?

根据题目描述,我们知道给定的节点i可能有多个儿子节点,不妨假设其有m个儿子节点。

则可以表示为,在m个儿子上一共选择k-1个节点,使得选择的节点权值之和最大。

举个例子,比如样例:

样例的树结构

我们在计算f[1][4]时,需要考虑对于以2,3,4分别为根节点子树,一共选择3个节点,来使得权值最大。也就是说在2子树中选择x个节点,3子树中选择y个节点,4子树中选择z个节点,使得x+y+z=3

很显然,该问题同样是一个动态规划问题:

建立状态g[i][j]g[i][j]表示前i个儿子选择j个节点时最大权值,则有状态转移过程:

g[i][j] = max{g[i - 1][j - k] + f[childId][k] | k = 0 .. j}

如果你有做过hiho一下第67期,你会发现这个子问题其实是一个泛化背包问题

因此我们可以得到整个动态规划的过程:

// 初始化f数组
f[][] = -1;

dp(root, k):
    If (k == 0) Then
        Return 0;
    End If
    If (f[root][k] != -1) Then
        // 由于可能多次调用dp(root,k)
        // 所以这里采用了记忆化的思想
        Return f[root][k];
    End If
    // 不选择该节点
    f[root][k] = 0;
    // 选择该节点
    g[][] = 0;  // 初始化g数组,需要注意g为该函数的局部变量
    For i = 1 .. m  // 枚举子节点
        For j = 0 .. k - 1
            For t = 0 .. j
                If g[i][j] < dp(child[i], t) + g[i-1][j-t] Then
                    g[i][j] = dp(child[i], t) + g[i-1][j-t];
                End If
            End For
        End For
    End For
    If (f[root][k] < g[m][k-1] + w[root]) Then
        f[root][k] = g[m][k-1] + w[root];
    End If
    Return f[root][k];

接下来我们处理条件二:其中有K个节点是必须要选择的

因为选择这K个节点,一定会选择它们所有的祖先节点。所以我们不妨先将这个K个节点和其祖先节点标记出来,并统计个数。

我们可以用下面这段代码来标记:

bool must[ N ]; // must[i]表示,该节点是否必须被选择
mark(i):
    must[i] = true;
    While (i != null)
        If (father[i] is not null) Then
            must[ father[i] ] = true;
        Else
        i = father[i];
    End While

最后我们统计所有被标记的节点数量,若其总数大于 M,那么显然是无解的。因为无论怎么选择都会超过 M个节点。

若总数小于等于 M,则表示是有可行解的。那么又有个新的问题:如何保证我们dp的过程中,一定能够把这些标记的点都选上。

我们需要在过程中,将不合法的状态标记出来,使得最后得到解一定是合法的,也就是包含所有必须选择的节点。

比如must[root] = true,那么f[root][0]一定就是一个不合法的状态,因为至少需要k=1,才能把root节点选择上。

我们建立一个标签INVALID来表示这些不合法的状态,改进后的dp函数代码:

// 初始化f数组
f[][] = -1;

dp(root, k):
    If (k == 0) Then
        If (must[root]) Then
            return INVALID;
        End If
        Return 0;
    End If
    If (f[root][k] != -1) Then
        // 由于可能多次调用dp(root,k)
        // 所以这里采用了记忆化的思想
        Return f[root][k];
    End If

    // 不选择该节点
    If (must[ root ]) Then
        f[root][k] = INVALID;
    Else
        f[root][k] = 0;
    End If

    // 选择该节点
    g[][] = 0;  // 初始化g数组,需要注意g为该函数的局部变量
    For i = 1 .. m  // 枚举子节点
        For j = 0 .. k - 1
            g[i][j] = INVALID; // 先假设不合法
            For t = 0 .. j
                If (dp(child[i], t) != INVALID and g[i-1][j-t] != INVALID) Then
                    If (g[i][j] == INVALID or g[i][j] < dp(child[i], t) + g[i-1][j-t]) Then
                        g[i][j] = dp(child[i], t) + g[i-1][j-t];
                    End If
                End If
            End For
        End For
    End For

    If (g[m][k-1] == INVALID) Then
        f[root][k] = INVALID;
    Else
        f[root][k] = g[m][k-1] + w[root];
    End If

    Return f[root][k];

另外还有一种比较取巧的方法:

由于我们动态规划得到的结果一定是最大解,所以我们只需要让最大解一定会选择这些must[i]=true的节点即可。

所以我们不妨给must[i]=true的节点加上一个很大的权值MOD。由于本题中w[i]不超过100,节点数量也不超过100,最大可能的解也就不超过10000。因此我们我们不妨设MOD = 1000000

这样在不改变dp函数情况下得到的解f[1][M],只要进行一次模运算,即f[i][M] % MOD就是该题实际的解了。

最后我们再来总结一下本题的解题过程:

  1. K个必须经过的节点执行mark()函数;
  2. 统计被标记为true的节点数量,若大于M则返回-1
  3. 若采用增加权值的方法,则对被标记为true的节点,权值增加MOD;否则直接进行第4步;
  4. 执行dp(1,M),得到f[1][M]

#1104 : Suzhou Adventure

1.题目给出若干个村庄,和这些村庄的分数,给出需要参观的村庄的总数和一定参观的村庄,求出怎么安排参观的村庄以达到最大的分数。

2.这道题目实则是一个树形动态规划的题目,我们对题目进行转化的话,会得到:

选择一定需要参观的村庄的时候,它的父亲节点也必定选取。

我们首先建立状态f[i][k]f[i][k]表示以i节点为根的子树,选择k个村庄能够得到的最大分数值。状态转移方程如下:

  • 选择i村庄:f[i][k]等于村庄i的分数加上所有子节点选择k-1个节点的最大分数值
  • 不选择i村庄:f[i][k] = 0

3.其中村庄的分数最大为100,村庄数量最多为100,所以总分最多是10000,我们给一定需要参观的村庄加上一个比较大的分数100000,这样,在动态规划的过程中,我们就会一定选取这些分数大的村庄(不一定全选,但是一旦可以到达,则必选)。

4.由于是树形,我们需要注意在统计子节点的分数时,不能反过来涉及到父节点,所以在添加边的时候,为单向边。

5.最终判断是否能够有solution时,我们判断f[1][k]/100000是否等于推荐参观村庄的数量,如果是,则输出f[1][k],否则输出-1。

更详细的解答过程可以参考:http://maybi.cn/wp_siukwan/?p=820

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

Little Hi is taking an adventure in Suzhou now. There are N beautiful villages in Suzhou which are numbered from 1 to N. They connected by N-1 roads in such a way that there is excactly one way to travel from one village to another. Little Hi gives each village a score according to its attractiveness. He is visiting village 1 now and plans to visit excatly M villages (including village 1) and maxize the total score of visited villages. Further more, K villages are recommended by Little Ho. He does not want to miss these recommended villages no matter what their attractiveness scores are.

Note that Little Hi visits every village on his travel route. Passing a village without visiting it is not allowed. Please find the maximum total score Little Hi can get.

输入

The first line contains 3 integers N(1 <= N <= 100), K(1 <= K <= 5) and M(1 <= M <= N), representing the number of villages, the number of recommended villages and the number of villages Little Hi plans to visit.
The second line contains N integers, the attractiveness scores of villages. The scores are between 1 to 100.
The third line contains K integers, the list of recommended villages.
The following N-1 lines each contain two integers a and b, indicating that village a and village b are connected by a road.

输出

The maximum scores Little Hi can get. If there is no solution output -1.

样例输入
5 2 4
1 2 3 4 5
3 4
1 2
1 3
1 4
2 5
样例输出
10

AC代码:
[c language=”++”]
//#include<string>
//#include <iomanip>
#include<fstream>
//#include<set>
//#include<queue>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
//#include <sstream>
//#include "func.h"
//#include <list>
#include<stdio.h>
#include<iostream>
#include<string>
#include<memory.h>
#include<limits.h>
//#include<stack>
#include<vector>
#include <algorithm>
using namespace std;

/*
5 2 2
1 2 3 4 5
3 5
1 2
1 3
1 4
2 5

*/

#define MOD 100000
struct VillageNode{
int score;
vector<int> nb;
VillageNode() :score(0), nb(0){};
};
int dfs(vector<VillageNode>&village,vector<vector<int>>&f, int root, int k)
{
if (k == 0)
return 0;
else if (f[root][k] != -1)
return f[root][k];//如果之前已经计算过f[root][k],则直接返回,不用遍历

//不选择这个节点的时候
f[root][k] = 0;

//选择这个节点的时候

int m = village[root].nb.size();

//建立状态g[i][j],g[i][j]表示前i个儿子选择j个节点时最大权值
//g[i][j] = max{g[i – 1][j – t] + f[childId][t] | t = 0 .. j}
vector<vector<int>> g(m + 1, vector<int>(k, 0));

//枚举root的孩子节点,需要注意的是,枚举的是儿子节点,不能使用双向边
for (int i = 1; i <= m; i++)
{
//因为选择当前节点,所以最多是求k-1个儿子的分值总和
for (int j = 0; j < k; j++)
{
for (int t = 0; t <= j; t++)
{
int child = village[root].nb[i-1];
if (g[i][j] < dfs(village, f, child, t) + g[i – 1][j – t])
{
g[i][j] = dfs(village, f, child, t) + g[i – 1][j – t];
}
}
}
}

if (f[root][k] < g[m][k – 1] + village[root].score)
f[root][k] = g[m][k – 1] + village[root].score;
return f[root][k];

}
/*
函数名 :main
函数功能:主函数
*/
int main(void)
{
int villageCount;
int recommendedVillageCount;
int planCount;

scanf("%d %d %d", &villageCount, &recommendedVillageCount, &planCount);

vector<VillageNode> village(villageCount+1);
//输入各个村庄的分数
for (int i = 1; i <=villageCount; i++)
{
scanf("%d", &village[i].score);
}

//输入推荐的村庄
for (int i = 0; i < recommendedVillageCount; i++)
{
int tmp;
scanf("%d", &tmp);
village[tmp].score += MOD;
}

//输入邻居关系
for (int i = 0; i < villageCount – 1; i++)
{
int a, b;
scanf("%d %d", &a, &b);
village[a].nb.push_back(b);
//需要注意的是,浏览的是儿子节点,不能使用双向边
//village[b].nb.push_back(a);
}

vector<vector<int>>f(villageCount + 1, vector<int>(planCount + 1, -1));

dfs(village, f, 1, planCount);
if (f[1][planCount] / MOD == recommendedVillageCount)
printf("%d\n", f[1][planCount] % MOD);
else
printf("-1\n");

return 0;
}

[/c]