文章目录
HDU-3410 Passing the Message
题意
有 n n n 个小孩要传递信息,传递信息只能从高个子的传递给矮个子的
问第 i i i 个小孩可以传递到最左边的哪个小孩,最右边的哪个小孩
思路
要算个数,可以正向计算左边的,逆向计算右边的
维护单调递减的序列
每次弹出的时候即使可以将信息传递到的小孩
Code
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 5;
// 维护单调递减的栈
int res[2][maxn];
int st[maxn];
int a[maxn];
void solve(int n) {
int ans, top;
top = 0;
for(int i = 1; i <= n; ++i) {
ans = 0;
while(top > 0 && a[st[top - 1]] <= a[i])
top--, ans = st[top];
st[top++] = i, res[0][i] = ans;
}
top = 0;
for(int i = n; i >= 1; --i) {
ans = 0;
while(top > 0 && a[st[top - 1]] <= a[i])
top--, ans = st[top];
st[top++] = i, res[1][i] = ans;
}
}
int main() {
int T;
scanf("%d", &T);
for(int t = 1; t <= T; ++t) {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
printf("Case %d:\n", t);
solve(n);
for(int i = 1; i <= n; ++i)
printf("%d %d\n", res[0][i], res[1][i]);
}
return 0;
}
HDU-4252 A Famous City
题意
n n n 个建筑的图片,每个图片有一个高度,高度可以为 0 0 0
问从左到右,至少可以表示多少个建筑
举个栗子
1 3 6 3 4
上面这个至少就有 4 4 4 个
思路
可以发现每弹出一个,后面的连续建筑和前面的连续建筑会分割开,则建筑数量会增加 1 1 1
利用单调栈,维护一个单调递增的栈
最后栈中剩下的个数是高度不同的,有几个不同高度就要有几个建筑
再加上弹出的建筑数 a n s ans ans,就可以得到最终结果
Code
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
// 维护单调递增栈
int st[maxn];
int a[maxn];
void solve(int n) {
int top = 0, ans = 0;
for(int i = 1; i <= n; ++i) {
while(top > 0 && a[st[top]] > a[i])
top--, ans++;
if(a[i] && (top == 0 || a[i] > a[st[top]]))
st[++top] = i;
}
printf("%d\n", top + ans);
}
int main() {
int T = 0, n;
while(~scanf("%d", &n)) {
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
printf("Case %d: ", ++T);
solve(n);
}
return 0;
}
HDU-1506 Largest Rectangle in a Histogram
HDU-1506 Largest Rectangle in a Histogram
题意
求最大连续矩形面积
思路
维护单调递增的栈
若弹出则计算此块矩形的面积,并记录弹出的矩形宽度和
则当前要弹入栈顶的是 弹出的宽度和+要弹入的矩形的宽度
这里循环的时候要多加一个空的循环
为了计算最后剩余在栈内的矩形的面积
Code
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
// 维护单调递增的栈
ll st[maxn];
ll width[maxn];
ll h[maxn];
void solve(int n) {
ll ans = 0; int top = 0; h[n + 1] = 0;
for(int i = 1; i <= n + 1; ++i) {
if(h[i] > st[top]) {
st[++top] = h[i], width[top] = 1;
}else {
ll wSum = 0;
while(st[top] > h[i]) {
wSum += width[top];
ans = max(ans, wSum * st[top]);
top -= 1;
}
st[++top] = h[i], width[top] = wSum + 1;
}
}
printf("%lld\n", ans);
}
int main() {
int n;
while(~scanf("%d", &n) && n) {
for(int i = 1; i <= n; ++i)
scanf("%lld", &h[i]);
solve(n);
}
return 0;
}
ZOJ-2642 Feel Good
题意
求最大连续矩形面积,每个矩形的面积为 最矮的矩形高度 × \times × 这块连续矩形的高度和
(其实我也没看过题目,看样例猜的题意)
最后输出矩形面积以及矩形的左右端点
如果有多个输出任意一个即可
思路
跟上一题很像
直接设置每个矩形的宽度为他们的高度
剩下的就是套用最大连续矩形面积的模板就好了
在计算最大面积的时候,可以记录此时的右端点
要输出的时候向前找到左端点
Code
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
// 维护单调递增的栈
ll st[maxn];
ll width[maxn];
ll h[maxn];
void solve(int n) {
ll ans = 0, tmp;
int top = 0, l, r; h[n + 1] = 0;
for(int i = 1; i <= n + 1; ++i) {
if(h[i] > h[st[top]]) {
st[++top] = i, width[top] = h[i];
}else {
ll wSum = 0; int tr = st[top];
while(h[st[top]] > h[i]) {
wSum += width[top];
if(wSum * h[st[top]] > ans)
tmp = wSum, r = tr, ans = wSum * h[st[top]];
top -= 1;
}
st[++top] = i, width[top] = wSum + h[i];
}
}
l = r;
while(tmp > 0) tmp -= h[l], l--;
printf("%lld\n%d %d\n", ans, l + 1, r);
}
int main() {
int n;
while(~scanf("%d", &n) && n) {
for(int i = 1; i <= n; ++i)
scanf("%lld", &h[i]);
solve(n);
}
return 0;
}
HDU-6319 Problem A. Ascending Rating ★
HDU-6319 Problem A. Ascending Rating
题意
长度为 n n n 的数组 a [ ] a[] a[]
给出前 k k k 个 a [ i ] a[i] a[i]
剩下的数由公式 a [ i ] = ( a [ i − 1 ] × p + q × i + r ) m o d M O D a[i] = (a[i -1] \times p + q \times i + r) \mod MOD a[i]=(a[i−1]×p+q×i+r)modMOD 来计算
求 A = ∑ k ≤ i ≤ n ( m a x ( a i , a i + 1 , . . . , a i + k − 1 ) A = \sum_{k \leq i \leq n}(max(a_i, a_{i + 1}, ... , a_{i + k - 1}) A=∑k≤i≤n(max(ai,ai+1,...,ai+k−1) ^ i ) i) i)
B = ∑ k ≤ i ≤ n ( c o u n t B = \sum_{k \leq i \leq n}(count B=∑k≤i≤n(count ^ i ) i) i)
其中 c o u n t count count 表示该子区间 [ i , i + k − 1 ] [i, i + k - 1] [i,i+k−1] 从左到右最大值修改的次数
思路
A A A 的计算可以直接维护单调递减的队列来求
而计算修改次数,可以发现对于后面的数如果最大值更改了,对前面的数是不会有影响的
反之如果前面的最大值有所修改,对后面的子区间修改次数是有可能产生影响的
因此可以考虑逆序,然后求修改次数
修改次数即为当前队列中元素的个数
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7 + 5;
// 维护单调递减的队列
int qu[maxn];
int a[maxn];
void solve(int n, int m) {
ll A = 0, B = 0;
int head = 1, tail = 0;
for(int i = n; i >= 1; --i) {
while(tail >= head && a[qu[tail]] <= a[i])
tail--;
qu[++tail] = i;
if(n - i + 1 < m) continue;
while(tail >= head && qu[head] >= i + m)
head++;
A += (a[qu[head]] ^ i) * 1ll;
B += ((tail - head + 1) ^ i) * 1ll;
}
printf("%lld %lld\n", A, B);
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n, m, k, p, q, r, mod;
scanf("%d%d%d%d%d%d%d", &n, &m, &k, &p, &q, &r, &mod);
for(int i = 1; i <= k; ++i)
scanf("%d", &a[i]);
for(int i = k + 1; i <= n; ++i)
a[i] = (a[i - 1] * 1ll * p + q * 1ll * i + r * 1ll) % mod;
solve(n, m);
}
return 0;
}
HDU-3530 Subsequence ★
题意
求一个长度最大的区间,该区间的 m ≤ m \leq m≤ (最大值 - 最小值) ≤ k \leq k ≤k
思路
维护两个单调队列,一个维护最大值,一个维护最小值
对于不满足 (最大值 - 最小值) ≤ k \leq k ≤k 的,将队列所存的位置从前往后依次弹出,位置较前面的先弹出
如果差值也满足 > = m >= m >=m ,就取最大值
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7 + 5;
int q1[maxn]; // 最大值 递减
int q2[maxn]; // 最小值 递增
int a[maxn];
void solve(int n, int m, int k) {
int h1 = 1, t1 = 0, h2 = 1, t2 = 0;
int ans = 0, pos = 0;
for(int i = 1; i <= n; ++i) {
// pos = 0;
while(h1 <= t1 && a[q1[t1]] <= a[i]) t1--;
while(h2 <= t2 && a[q2[t2]] >= a[i]) t2--;
q1[++t1] = i, q2[++t2] = i;
while(h1 <= t1 && h2 <= t2 && a[q1[h1]] - a[q2[h2]] > k) {
if(q1[h1] < q2[h2]) pos = q1[h1++];
else pos = q2[h2++];
}
if(h1 <= t1 && h2 <= t2 && a[q1[h1]] - a[q2[h2]] >= m)
ans = max(ans, i - pos);
}
printf("%d\n", ans);
}
int main() {
int n, m, k;
while(~scanf("%d%d%d", &n, &m, &k)) {
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
solve(n, m, k);
}
return 0;
}
POJ-2823 Sliding Window
题意
求区间长度为 m m m 的最大值和最小值
思路
求最大值维护单调递减的队列
最小值维护单调递增的队列
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
int q[maxn]; // 最大值 递减 // 最小值 递增
int a[maxn];
int ans[maxn];
void getMax(int n, int m) {
int head = 1, tail = 0;
for(int i = 1; i <= n; ++i) {
while(head <= tail && a[q[tail]] <= a[i]) tail--;
q[++tail] = i;
while(head <= tail && q[head] + m <= i) head++;
ans[i] = a[q[head]];
}
for(int i = m; i <= n; ++i)
printf("%d%c", ans[i], i == n ? '\n' : ' ');
}
void getMin(int n, int m) {
int head = 1, tail = 0;
for(int i = 1; i <= n; ++i) {
while(head <= tail && a[q[tail]] >= a[i]) tail--;
q[++tail] = i;
while(head <= tail && q[head] + m <= i) head++;
ans[i] = a[q[head]];
}
for(int i = m; i <= n; ++i)
printf("%d%c", ans[i], i == n ? '\n' : ' ');
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
getMin(n, m);
getMax(n, m);
return 0;
}
POJ-3250 Bad Hair Day [思维] ★
题意
长度为 n n n 的数组 a [ ] a[] a[],第 i i i 只牛可以看到右边连续比他矮的几只牛
(即如果出现一只比他高的,右边的牛也都看不到)
问总共可以看到的牛的次数和
思路
换一个角度也就是求每只牛被看到的次数和
每只牛都是被比他高的牛看到的
因此维护一个牛的高度的最大值,单调递减的栈
每次栈中的元素数量就是第 i i i 头牛被看到的次数
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 8e4 + 5;
int st[maxn]; // 最大值 递减
int main() {
int n, x, top = 0; ll ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &x);
while(top > 0 && st[top] <= x) top--;
ans += top * 1ll;
st[++top] = x;
}
printf("%lld\n", ans);
return 0;
}
HDU-6444 Neko’s loop [单调队列][裴蜀定理][最大连续子段和] ★★★
HDU-6444 Neko’s loop
参考博客:
hdu 6444 网络赛 Neko’s loop(单调队列 + 裴蜀定理)题解
HDU 6444 Neko’s loop(思维+长度不超过L的最大子段和)
题意
一个长度为 n n n 的数组 a [ ] a[] a[],首尾相连循环, a [ i ] a[i] a[i] 表示这个位置上的价值(可能为负值)
现在开始取一些值,开始的位置可以任意选择
选择后下一次的位置为 ( i + k ) % m (i + k) \% m (i+k)%m,已经走过的位置再次走即再次获得它的价值
现在有 m m m 点能量值,即相当于最多可以移动 m m m 次,问要得到 ≥ s \ge s ≥s 的价值,最初给定的价值需要多少(这个需要为非负数)
思路
由于是首尾相连的循环,且每次增加的位移是固定的,因此必然存在循环节。
根据裴蜀定理, n x + k y = t nx + ky = t nx+ky=t,可以知道循环节的数量应该为 g = g c d ( n , k ) g = gcd(n, k) g=gcd(n,k),则循环节的长度应该为 l e n = n / g c d ( n , k ) len = n / gcd(n, k) len=n/gcd(n,k)
(其实我一直不是很能理解这个地方,看了很多篇博客都没有详细说的。。_(:з」∠)_大概只是我太菜了吧)
分完组之后可以对每一组寻找可以取到的最大值
对于每个循环节,取到最大值是有两种情况的( s u m sum sum 为该循环节的和)
- m % l e n ! = 0 m \% len != 0 m%len!=0,这个时候应该找长度 ≤ ( m % l e n ) \leq (m \% len) ≤(m%len) 所能得到的最大价值,再加上 ( m / l e n ) × s u m (m / len) \times sum (m/len)×sum
这个其实存在一种情况会取不到最优的- m % l e n = = 0 m \% len == 0 m%len==0,这个时候应该找长度 ≤ l e n \leq len ≤len 所能得到的最大价值,再加上 ( m / l e n − 1 ) × s u m (m / len - 1) \times sum (m/len−1)×sum
其实相当于先求出长度 ≤ \leq ≤ 当前确定的最大长度的最大字段和,再加上 ( m / l e n ) × s u m (m / len) \times sum (m/len)×sum 就是可以取到的最大值
当然后面加上循环节的和必须要是循环节的和 s u m > 0 sum > 0 sum>0 的情况下,不然越加越少
接下来是求最大连续子段和,利用单调队列
这里需要两倍的循环节,这样才能计算出首尾相连的最大连续子段和
维护以 i i i 为子段右端点, q [ h e a d ] q[head] q[head] 为子段左端点的最大连续子段
若 s u m [ k ] < s u m [ j ] & & k > j sum[k] < sum[j] \& \& k > j sum[k]<sum[j]&&k>j,这个时候应该选择 k k k 做左端点可以使子段和更优(忘记哪个博客看到的了)
因此单调队列维护 s u m [ j ] sum[j] sum[j] 的最小值,单调递增队列
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
const int inf = 0x3f3f3f3f;
const int maxn = 8e4 + 5;
ll a[maxn];
ll b[maxn]; // 同一组内的值
ll q[maxn]; // 维护左端点
ll sum[maxn]; // 前缀和
bool vis[maxn]; // 标记是否算过
ll cal(int sz, int len) { // 最大子段和模板
ll ans = 0;
int head = 1, tail = 0;
for(int i = 1; i <= len * 2; ++i) {
// 以 i 为右端点, q[head] 为左端点
// 若 k > j && sum[k] < sum[j] -> k 做左端点更优
// 因此维护单调递增队列
sum[i] = b[i] + (i == 1 ? 0 : sum[i - 1]);
if(i <= sz)
ans = max(ans, sum[i]);
while(head <= tail && q[head] + sz < i)
head++;
if(head <= tail)
ans = max(ans, sum[i] - sum[q[head]]);
while(head <= tail && sum[q[tail]] >= sum[i])
tail--;
q[++tail] = i;
}
return ans;
}
ll solve(int m, int len) {
ll ans = 0, ans1, ans2;
for(int i = 1; i <= len; ++i) ans += b[i]; // 该循环节的和
ans1 = cal(m % len, len), ans2 = cal(len, len);
if(ans > 0) {
if(m / len >= 1) ans1 += ans * (m / len * 1ll);
if(m / len >= 2) ans2 += ans * (m / len - 1) * 1ll;
}
return ans1 > ans2 ? ans1 : ans2;
}
int main() {
int T;
scanf("%d", &T);
for(int t = 1; t <= T; ++t) {
ll n, s, m, k;
scanf("%lld%lld%lld%lld", &n, &s, &m, &k); // m 点能量
for(int i = 0; i < n; ++i) {
scanf("%lld", &a[i]); vis[i] = false;
}
int num = __gcd(n, k), len = n / num; // 循环节个数, 循环节长度
ll res = -INF;
for(int i = 0, g = 1; g <= num && i < n; ++i) {
if(vis[i]) continue;
vis[i] = true;
for(int j = 1, pos = i; j <= len; ++j) {
b[j] = b[j + len] = a[pos]; // 两倍长
pos = (pos + k) % n, vis[pos] = true;
}
res = max(res, solve(m, len)), g += 1;
}
printf("Case #%d: %lld\n", t, res >= s ? 0 : s - res);
}
return 0;
}
P2216 [HAOI2007]理想的正方形 [二维单调队列]
P2216 [HAOI2007]理想的正方形
参考博客
BZOJ 1047 [HAOI2007]理想的正方形
题意
原矩阵大小为 a × b a \times b a×b
求子矩阵 n × n n \times n n×n 的 m i n ( 最 大 值 − 最 小 值 ) min(最大值 - 最小值) min(最大值−最小值)
思路
二维单调队列模板
先维护 1 行 n 列的矩阵最大/小值
再维护 n 行 n 列的矩阵最大/小值
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 5;
int re[2][maxn][maxn];
int a[maxn][maxn];
int g[maxn][maxn];
int q[maxn];
int n, m, k;
void solve(int f) {
for(int i = 1; i <= n; ++i) {
int L = 1, R = 0;
for(int j = 1; j <= m; ++j) {
while(L <= R && j - q[L] >= k) L++;
if(f) while(L <= R && a[i][q[R]] >= a[i][j]) R--;
else while(L <= R && a[i][q[R]] <= a[i][j]) R--;
q[++R] = j, g[i][j] = a[i][q[L]];
}
}
for(int j = k; j <= m; ++j) {
int L = 1, R = 0;
for(int i = 1; i <= n; ++i) {
while(L <= R && i - q[L] >= k) L++;
if(f) while(L <= R && g[q[R]][j] >= g[i][j]) R--;
else while(L <= R && g[q[R]][j] <= g[i][j]) R--;
q[++R] = i;
re[f][i][j] = g[q[L]][j];
}
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
scanf("%d", &a[i][j]);
solve(0);
solve(1);
int ans = 1<<30;
for(int i = k; i <= n; ++i)
for(int j = k; j <= m; ++j)
ans = min(ans, re[0][i][j] - re[1][i][j]);
printf("%d\n", ans);
return 0;
}