题目太长了就不贴了。
题目大意是给你前k个数,让你用题目给的公式生成后n - k个数,形成一个长度为n的数列。
之后在上面滑动一个长度为m的区间(窗口),对每个区间求两个东西:
1、区间最大值
2、第一个数后有多少个数比它大。
最后分别输出每个区间上述两个值与区间编号i之间异或后的和。
经典的滑窗问题,需要倒着维护一个递减的单调队列。
单调队列的队头就是要找的最大的元素,而要求的第二个值,则可以用尾指针 - 头指针 + 1求得。
注意,这里的头尾指针和一般队列的头尾指针不一样,头指针的确指向队头,但是尾指针指向的是最新一个加进队列里的元素。
这就是为什么要倒着来维护,因为这样才能保证尾指针指在实际的第一个元素上。
ac代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7 + 5;
int a[maxn], que[maxn];
int main() {
int T, n, m, k, p, q, r, mod;
scanf("%d", &T);
while(T--) {
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] = (1LL * p * a[i - 1] + 1LL * q * i + r) % mod;
}
ll ans = 0, ans2 = 0;
ll front = 1, rear = 0;
for(int i = n; i > 0 ; i--) {
// 把数字的索引放到单调队列里应该在的位置
while(front <= rear && a[i] >= a[que[rear]]) {
rear--;
}
que[++rear] = i;
if(i + m - 1 <= n) {
// 删掉已经不在当前区间里的元素
while(que[front] >= i + m) {
front++;
}
ans += i ^ a[que[front]];
ans2 += i ^ rear - front + 1;
}
}
printf("%lld %lld\n", ans, ans2);
}
return 0;
}
在滑窗问题上单调队列很优秀,从上面的代码就可以看到,新元素入队时总是会覆盖旧元素,这是因为如果一个已经在队里的元素(代表着他比新入队的元素晚出现,因为是倒着来的)小于等于新元素,根本就无需再考虑这个元素,直接删掉就可以了。