Alice和Bob在玩游戏。
游戏一共有m轮,Alice手里有一个长度为n的序列a,和常数K,每一轮游戏,Alice会从序列中取出一个连续区间 [L,R]给Bob,问Bob这个区间中存在多少个连续子区间满足,区间中不同的数的个数不小于K。Bob全部回答正确了则Bob赢,否则是Alice赢。
现在你需要帮助Bob赢得游戏。
题意:
求区间内存在多少个连续子区间满足其中不同的数大于等于K个
数量级1e5
题解:
一开始看到区间内不同的数大于等于K个,可以用set维护某区间内不同的数出现的位置,保证set的size为K时,便可根据set中第一个和最后一个的下标求出所有最小区间(即刚好满足set.size()==K)的区间(首尾数不同)(set内自动排序,这个功能很强,可以保证每次加入时动态更新复杂度为logn)
然后为了方便查询,定义数组f[i]为 [f[i],i]代表以i位置为结尾的最小K区间,并求f[i]前缀和sum[i],则每次查询L,R时,ans=(f[j]-L+1),j为L R中所有极小区间的末尾.
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
const ll N = 1e5 + 7;
ll n, m, K;
ll a[N], b[N], cnt, f[N], pos[N], in, ans, l, r, sum[N];
map<ll, ll>mp;
set<ll>st;
void pre() {
sort(b + 1, b + 1 + n);
b[0] = -1;
for (ll i = 1; i <= n; i++)//离散化
if (b[i] != b[i - 1]) {
cnt++;
mp[b[i]] = cnt;
}
for (ll i = 1; i <= n; i++)
a[i] = mp[a[i]];
for (ll i = 1; i <= n; i++) {
if (pos[a[i]] == 0) {//新数加入集合
pos[a[i]] = i;
st.insert(i);
in++;
}
else {
if (st.find(pos[a[i]]) != st.end()) {
st.erase(st.find(pos[a[i]])); //更新集合中此数的最后出现位置
in--;
}
pos[a[i]] = i;
st.insert(i);
in++;
}
if (in == K) {
auto x = st.begin();//保证集合大小为K,更新f[i]
f[i] = *x;
}
else if (in == K + 1) {
auto x = st.begin();
st.erase(x);
auto y = st.begin();
f[i] = *y;
in--;
}
sum[i] = sum[i - 1] + f[i];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m >> K;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
}
pre();
while (m--) {
cin >> l >> r;
ll li = l, ri = r;
l = min(li ^ ans, ri ^ ans) + 1;
r = max(li ^ ans, ri ^ ans) + 1;
ans = 0;
int pos = lower_bound(f + l, f + r + 1, l) - f;//寻找满足条件的最小的i
if (pos != r + 1) {
ans = sum[r] - sum[pos - 1] + r - pos + 1 - l * (r - pos + 1);
}
else {
ans = 0;
}
cout << ans << "\n";
}
}