luogu 1972
提高+/ 省选-
题面:
输入长度为N(N<50000)的数列,进行M(M<=200000)次询问,每次询问一个区间,输出数列对应区间内出现不同数字的个数。
例:
1 2 3 4 3 5
[1, 3] : 3
[2, 5] : 4
[1, 5] : 5
思路:
离线查询。
1、读入数列,第一次读入时,将每个数第一次出现的位置启用。
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d", &a[i]); //a[i]:原数列中i的值
v[a[i]].push_back(i); //v[a[i]]:a[i]在数列中出现的位置
if(v[a[i]].size()>1) continue;
t[i] = 1; //t[i]:位置i上的数是否启用,供线段树查询使用; 初始值为0(未启用),一旦位置i的数启用,则变为1(启用)
next[a[i]] = 1; //next[a[i]]:下一个未启用的a[i]在v[a[i]]中的位置
}
...
}
2、将每次询问记录,并按照左边界排序。
struct tQues //将每个询问作为结构体储存
{
int left, right, num; //num:记录原顺序
bool operator < (const tQues& rhs) const
{
if(left!=rhs.left) return left<rhs.left;
return right<rhs.right;
} //重载运算符,以便按照左边界(left)排序
} q[MAXN*4]
int main()
{
...
scanf("%d", &m);
for(int i=1; i<=m; i++)
{
int tmp1, tmp2;
scanf("%d%d", &tmp1, &tmp2);
q[i].left = min(tmp1, tmp2);
q[i].right = max(tmp1, tmp2);
q[i].num = i;
}
sort(q+1, q+m+1); //排序
...
}
3、按顺序处理询问,处理时记录上一次询问的左边界last_l(初值为1)。每次查询时,[last_l, l)区间内的值永远不会在接下来的查询内出现。我们可以遍历[last_l, l),对于其中的每一个值,启用它的下一个出现位置。
int main()
{
...
int last_l = 1;
for(int i=1; i<=m; i++)
{
int l = q[i].left, r = q[i].right; //取l,r为当前询问的左、右边界
for(int k=last_l; k<l; k++) //遍历[last_l, l)
{
int tmp = a[k]; //取tmp为数列中k位置的值
if(next[tmp] >= v[tmp].size()) continue; //如果next超出了v.size(),意味着tmp的所有位置都已经启用,所以跳过
t[v[tmp][next[tmp]]] = 1; //启用tmp的下一个位置
st.change(1, 1, n, v[tmp][next[tmp]], 1); //对该位置进行单点修改
next[tmp] ++; //next[tmp]后移一位
}
ans[q[i].num] = st.querysum(1, 1, n, l, r); //ans[]按原序记录答案
last_l = l; //更新last_l
}
...
}
4、按原序输出
int main()
{
...
for(int i=1; i<=m; i++)
printf("%d\n", ans[i]);
return 0;
}