离散化、KMP和单调栈队列洛谷习题
离散化
vector alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, …n
}
P1496 火烧赤壁
给定每个起火部分的起点和终点,请你求出燃烧位置的长度之和。
输入格式
第一行一个整数,表示起火的信息条数 n。
接下来 n行,每行两个整数 a、b ,表示一个着火位置的起点和终点。
输出格式
输出一行一个整数表示答案。
输入输出样例
输入
3
-1 1
5 11
2 9
输出
11
对于全部的测试点,保证 1≤n≤ 2×104, −231 ≤a≤b<231,且答案小于 231。
题目基本思路:
①输入a,b数组的值,并将其写入待离散化的数组c中。
②c数组内的值进行从小到大排序,然后二分求出a[i]、b[i]对应的离散化的值, 并将a[i]到b[i]内的数值做上标记(令flag等于1,这里并没有令flag[b[i]]为1的原因是火只会烧到终点的前方,这里令flag为1表示该点以后的一个单位被火烧了)
③扫一遍flag数组,如果遇到了该点值为1,则将这一段燃烧长度累加到最终的结果上。
code:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2e4 + 10;
long n, ans, m = 1;
long a[N], b[N], c[2*N], flag[2*N];
int find(long x)
{
long l = 1, r = m;
while(l < r)
{
int mid = (l + r) >> 1;
if(x <= c[mid]) r = mid;
else l = mid + 1;
}
return r;
}
int main()
{
cin >> n;
for(long i = 1; i <= n; i ++)
{
scanf("%ld%ld", &a[i], &b[i]);
c[m++] = a[i], c[m++] = b[i];
}
sort(c + 1, c + m + 1);
for(long i = 1; i <= n; i ++)
{
a[i] = find(a[i]), b[i] = find(b[i]);
for(int j = a[i]; j < b[i]; j ++)
flag[j]= 1;
}
for(long i = 1; i <= m; i ++)
{
if(flag[i]) ans += c[i+1] - c[i];
}
printf("%ld",ans);
return 0;
}
P4122 [USACO17DEC]Blocked Billboard B
在平面直角坐标系中,有两个矩形(保证不相交),然后给出第三个矩形,求这两个矩形没有被第三个矩形遮住的部分的面积。
输入格式:
题目给出三个坐标,分别表示三个矩形的左下、右上坐标。
输入输出样例
输入
1 2 3 5
6 0 10 4
2 1 8 3
输出
17
算法的基本思路:
采用结构体的方式存储矩形的左下和右上坐标。将输入的三个矩形的横纵坐标分别写入X、Y数组中,并将这两个数组排序,三个矩形所在的区域离散成由(x[i], x[j])和(x[i + 1], y[j + 1])表示的几个小矩形,判断每一个小矩形是否在矩形A或B内且不在矩形C内,满足条件就将这一小块矩形的面积累加到结果中。
code:
#include <iostream>
#include <algorithm>
using namespace std;
struct Rectangle//(x1, y1)为左下坐标,(x2, y2)为右上的坐标
{
int x1, y1, x2, y2;
}A, B, C;
int Area(Rectangle t)
{
return (t.x2 - t.x1)*(t.y2 - t.y1);
}
bool check(Rectangle a, Rectangle b)//判断矩形a是否在矩形b内
{
return (a.x1 >= b.x1 && a.y1 >= b.y1
&& a.x2 <= b.x2 && a.y2 <= b.y2);
};
int X[10], Y[10];
int main()
{
cin >> A.x1 >> A.y1 >> A.x2 >> A.y2;
cin >> B.x1 >> B.y1 >> B.x2 >> B.y2;
cin >> C.x1 >> C.y1 >> C.x2 >> C.y2;
X[1] = A.x1, X[2] = A.x2, X[3] = B.x1;
X[4] = B.x2, X[5] = C.x1, X[6] = C.x2;
Y[1] = A.y1, Y[2] = A.y2, Y[3] = B.y1;
Y[4] = B.y2, Y[5] = C.y1, Y[6] = C.y2;
sort(X + 1, X + 7);
sort(Y + 1, Y + 7);
int ans = 0;
for(int i = 1; i < 6; i ++)
{
for(int j = 1; j < 6; j ++)
{
struct Rectangle R = {X[i], Y[j], X[i + 1], Y[j + 1]};
if((check(R, A) || check(R, B)) && !check(R, C))
ans += Area(R);
}
}
cout << ans << endl;
return 0;
}
KMP
P3375 【模板】KMP字符串匹配
算法的基本思路:
在模式串和主串匹配过程中,当遇到不匹配的字符时,对于主串和模式串中已对比过相同的前缀字符串,找到长度最长的相等前缀串,从而将模式串一次性滑动多位,并省略一些比较过程。在KMP算法中通过next数组来存储当两个字符不相等时模式串应该移动的位数。
算法的关键点:
如何生成next数组?next数组用来存模式串中前x个字符前缀和后缀相同的最长长度。 next[i] = j 表示以1为起点,j为终点的前缀和以i - j + 1为起点,i为终点的后缀相等,且此字符串的长度最长。用符号表示为p[1 ~ j] = p[(i - j + 1) ~ i].
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 100010, M = 1000010;
int n, m, ne[N];
char p[N], s[M];
int main()
{
cin >> (s + 1) >> (p + 1);
m = strlen(s + 1);
n = strlen(p + 1);
//求next数组的过程
for(int i = 2, j = 0; i <= n; i ++)
{
while(j && p[i] != p[j + 1]) j = ne[j];
if(p[i] == p[j + 1]) j ++;
ne[i] = j;
}
//kmp匹配过程
for(int i = 1, j = 0; i <= m; i ++)
{
while(j && s[i] != p[j + 1]) j = ne[j];
if(s[i] == p[j + 1]) j ++;
if(j == n)
{
printf("%d\n", i - n + 1);
j = ne[j];
}
}
for(int i = 1; i <= n; i ++)
{
printf("%d ", ne[i]);
}
return 0;
}
P4391 [BOI2009]Radio Transmission 无线传输
题目描述
给你一个字符串 s1,它是由某个字符串 s2不断自我连接形成的。但是字符串 s2是不确定的,现在只想知道它的最短长度是多少。
输入格式
第一行一个整数 L,表示给出字符串的长度。
第二行给出字符串 s1的一个子串,全由小写字母组成。
输出格式
仅一行,表示 s2的最短长度。
输入输出样例
输入
8
cabcabca
输出
3
算法思路:
因为ne[x]为前x个字符前缀和后缀相同的最长长度, 题目给出的字符串是以一个子串不断循环连接所得,ne数组保证后缀和前缀是完全相同的,所以后缀的第一位也是前缀的第一位字符(即最小循环子串的第一位字符),在保证了ne[N]存储的是最长长度的情况下,后缀起始的位置应该是一个最小周期子串的第二次循环起始位置,所以最小循环子串的长度为n - ne[n].
code:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1000010;
int n, ne[N];
char s[N];
int main()
{
cin >> n >> (s + 1);
for(int i = 2, j = 0; i <= n; i ++)
{
while(j && s[i] != s[j + 1]) j = ne[j];
if(s[i] == s[j + 1]) j ++;
ne[i] = j;
}
cout << n - ne[n] << endl;
return 0;
}
P3435 [POI2006]OKR-Periods of Words
解题思路 :
记Q的长度为i, a的长度为j, 要满足a是Q+Q的前缀,则必须s[(i + 1)~ j] = s[1~(j - i)] ,求的是最大周期长度,所以要找到最短的公共前后缀。ne[x]表示的是最长的公共前后缀,求解最短时就要不断的找 x 的ne[x](即while(ne[j]) j = ne[j];),到0之前的那个ne[x]即为最短的公共前后缀,此时该位置与最短公共前后缀相减即得最大的周期长度。
code:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1000010;
long n, ne[N];
char s[N];
int main()
{
cin >> n >> (s + 1);
for(long i = 2, j = 0; i <= n; i ++)
{
while(j && s[i] != s[j + 1]) j = ne[j];
if(s[i] == s[j + 1]) j ++;
ne[i] = j;
}
long ans = 0;
for(long i = 1; i <= n; i ++)
{
if(! ne[i]) continue;
long j = ne[i];
while(ne[j]) j = ne[j];
ne[i] = j;
ans += (i - j);
}
cout << ans;
return 0;
}
单调栈、队列
P1901 发射站
某地有 N个能量发射站排成一行,每个发射站 i 都有不相同的高度 Hi,并能向两边(两端的发射站只能向一边)同时发射能量值为 Vi的能量,发出的能量只被两边最近的且比它高的发射站接收。显然,每个发射站发来的能量有可能被 0 或 1 或 2 个其他发射站所接受。
请计算出接收最多能量的发射站接收的能量是多少。
输入格式
第 1 行一个整数 N。
第2到N+1行,第 i+1 行有两个整数 Hi 和Vi,表示第 i 个人发射站的高度和发射的能量值。
输出格式
输出仅一行,表示接收最多能量的发射站接收到的能量值。答案不超过 32 位带符号整数的表示范围。
输入输出样例
输入
3
4 2
3 5
6 10
输出
7
算法基本思路:
发射站只会向左边和右边第一个比它高的站发射能量,所以这题可以用单调栈来维护发射站高度的单调性。栈维护的高度是单调递减的,如果此时栈顶的元素没有新加进来的站高,那么栈顶就会给新加的站发射能量(向右发射完成),肯定就不能给后面的元素发射能量了,然后将栈顶元素删除,将新的元素加进来。新的元素加进来以后,会对他在栈中下面那个元素传输能量,栈中下面的元素是离它左边最近且比它高(向左发射完成)。
code:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e6 + 10;
int n, stk[N], tt = 0, h[N], v[N], r[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d%d", &h[i], &v[i]);
for(int i = 1; i <= n; i ++)
{
while(tt && h[stk[tt]] < h[i])
{
r[i] += v[stk[tt]];
tt --;
}
r[stk[tt]] += v[i];
stk[++tt] = i;
}
int ans = 0;
for(int i = 1; i <= n; i ++) ans = max(ans, r[i]);
cout << ans;
return 0;
}
P1638 逛画展
博览馆正在展出由世上最佳的 M 位画家所画的图画。
wangjy想到博览馆去看这几位大师的作品。
可是,那里的博览馆有一个很奇怪的规定,就是在购买门票时必须说明两个数字,
a和b,代表他要看展览中的第 a 幅至第 b 幅画(包含 a 和 b)之间的所有图画,而门票
的价钱就是一张图画一元。
为了看到更多名师的画,wangjy希望入场后可以看到所有名师的图画(至少各一张)。
可是他又想节省金钱。。。
作为wangjy的朋友,他请你写一个程序决定他购买门票时的 a 值和 b 值。
输入格式
第一行是 N 和 M,分别代表博览馆内的图画总数及这些图画是由多少位名师的画所绘画的,其后的一行包含 N 个数字,它们都介于 1 和 M 之间,代表该位名师的编号。
输出格式
a和 b(a<=b) 由一个空格符所隔开。
保证有解,如果多解,输出a最小的。
输入输出样例
输入
12 5
2 5 3 1 3 2 4 1 1 5 4 3
输出
2 7
算法思路:
这道题用到双指针和单调队列的思想,q[N]数组存放画家的编号,times[N]数组存放该滑动窗口内每个画家出现的次数。滑动窗口的头为hh, 尾为tt, 长度为length(本题就是寻找满足要求的最短窗口的头和尾)。对于存入的画家编号,如果该画家没有出现过,则待出现的画家数m减一,并将他的出现次数加一;队头对应的编号若再次出现,就向后移动队头指针,原来队头元素对应的画家次数减一。直到m等于0,且长度最短,输出队头和队尾。
code:
#include <iostream>
using namespace std;
const int N = 1000010, M = 2010;
int n, m, q[N], times[M], l, r;
int main()
{
cin >> n >> m;
int hh = 1, length = n;
for(int tt = 1; tt <= n; tt ++)
{
cin >> q[tt];
if(!times[q[tt]]) m --;
times[q[tt]] ++;
while(hh <= tt && times[q[hh]] > 1)
{
times[q[hh]] --;
hh ++;
}
if((m == 0) && (tt - hh + 1) < length)
{
length = tt - hh + 1;
l = hh, r = tt;
}
}
cout << l << " " << r << endl;
return 0;
}