[统计数对] 题解

来源:互联网 发布:网络咨询流程图 编辑:程序博客网 时间:2024/04/28 13:35
A1350. 数对统计 (罗剑桥)时间限制:1.0s   内存限制:256.0MB试题来源  IOI2012中国国家队训练问题描述  你得到了一个由 N 个非负整数构成的序列 A。  你需要回答关于这个序列的 Q 次询问。  每次询问将提供三个整数参数 v,a,b。你需要统计满足以下三个条件的整数  数对(i, j)的数目:1 <= i <= j <= N, a <= j-i+1 <= b, 并且对于任意i <= k <= j的整数k有A[k] >= v。其中A[k]表示序列A的第k项。输入格式  输入的第一行包括两个正整数 N, Q,分别表示序列 A 的项数和询问数。 第二行,N 个非负整数。其中第 i 个整数表示 A[i]。  接下来的 Q 行,每行 3 个整数 v, a, b,依次表示一次询问的三个参数。输出格式  输出 Q 行,每行一个非负整数。第 i 行的非负整数表示第 i 次询问 的答案。样例输入5 35 3 2 7 43 2 32 2 54 1 1样例输出2103数据规模和约定  20%的数据满足,1 <= N <= 1 000, 1 <= Q <= 1 000;  40%的数据满足,1 <= N <= 50 000, 1 <= Q <= 50 000;  60%的数据满足,1 <= N <= 100 000, 1 <= Q <= 100 000; 100%的数据满足,1 <= N <= 300 000, 1 <= Q <= 300 000, 0 <= A[i], v <= 1 000, 1 <= a, b <= 200 000.

乍看此题,以为是个数据结构题。使劲想也没有想出来什么数据结构可以支持这个,最后乱写全挂。
之后 Orz 了 LYP,觉得这真是一道非常非常好的题目,有非常非常多的值得我所学习的地方。

首先,并不用拘泥于数据结构题的想法,去写在线的算法,因为这里没有修改(没有修改 == 离线?)。
所以,很容易想到的就是把 A 序列和询问都按元素为关键字逆序排序,离线解决每一个询问。
这样一来,区间的产生就容易多了:对于每一个询问,所问的为 A[k] >= v,这就意味着比 v 大的是可以保留的。
所以,每次将大的取出,更新区间集合,然后就可以尽快回答当前关键字的询问,因为区间集合中的每一个元素都是满足条件的(不然就是 0 了)。
这样一来,不用去枚举每一个区间,这是降低时间复杂度的关键。

再者,对于一个询问(v, a, b)和一个合法的区间(s, t),易知其有一需要根据 l1 = b - a + 1 和 l2 = t - s + 1 分情况讨论的公式,但是分情况讨论不便于维护。
所以如何转化呢?首先易知,如果对于 i 满足 s <= i <= t, 则生成满足条件数对的个数易知其为 (l - i + 1) * (l - i + 2), l = t - s + 1 。
发现了什么?显然,如果对于 i, j 满足 s <= i <= j <= t, 则生成满足条件数对的个数为 (l - i + 1) * (l - j + 2) - (l - (j + 1) + 1) * (l - (j + 1) + 2), l = t - s + 1 。
证明显然,只需一减即可知。拆开此式,有 l * l + (3 - i * 2) * l + (i - 3) * i + 2 。记此式为 g(l, i) 。
又因为 s <= i <= t, 所以不讨论范围之外的 i 。又易知每次的询问即当前所有(合法)区间的 g(l, a) - g(l, b + 1) 值和。
所以当前的任务为:快速求出 Σ g(l, a), l >= a 。如何做呢?最容易为所想到的是:维护  Σ g(l, 1 .. n), 再得差求得。
观察 g(l, i), 易得所需要维护的为 Σ l * l, Σ l, Σ count,依次乘以 1, (3 - i * 2), (i - 3) * i + 2 即为 g(l, i) 。
想到了什么?树状数组。
每次如果要删除一个区间(合并时应删除原区间),就在该长度 l 的后缀数组中分别减去 l * l, l, 1 即可。添加同。
如何求 g(l, i)? 求出 1 .. i - 1 的区间和,再用一个总和减去即可。
如何回答询问?求出 g(l, a) - g(l, b + 1), 再分别存入每一次询问中即可。

至此,该问题已解决。

Code :

#include <cstdio>#include <cstdlib>#include <cstring>#include <climits>#include <iostream>#include <algorithm>typedef unsigned uint;typedef long long int64;typedef unsigned long long uint64;#define swap(a, b, t) ({t _ = (a); (a) = (b); (b) = _;})#define max(a, b, t) ({t _ = (a), __ = (b); _ > __ ? _ : __;})#define min(a, b, t) ({t _ = (a), __ = (b); _ < __ ? _ : __;})#define maxn 310000#define maxv 1100#define link(e, i, j) (next[++ adj] = e[i], to[e[i] = adj] = j)int n, m, u, v, a, b, adj = 1;int ufs[maxn], x[maxn << 1], y[maxn << 1], size[maxn];int e1[maxv], e2[maxv], next[maxn << 1], to[maxn << 1];int64 s[maxn], s1[maxn], s2[maxn], ans[maxn];int64 t, t1, t2;int find(int x){    return ufs[x] == x ? x : ufs[x] = find(ufs[x]);}void alter(int p, int d){    int64 d1 = d * p, d2 = d1 * p;    t += d, t1 += d1, t2 += d2;    for (; p <= n; p += p & - p)        s[p] += d, s1[p] += d1, s2[p] += d2;}int64 query(int p){    int64 d = 0, d1 = 0, d2 = 0, q = p + 1;    for (; p; p &= p - 1)        d += s[p], d1 += s1[p], d2 += s2[p];    d = t - d, d1 = t1 - d1, d2 = t2 - d2;    return d * (q * (q - 3) + 2) + (3 - (q << 1)) * d1 + d2;}int main(){    freopen("c.in", "r", stdin);    freopen("c.out", "w", stdout);        scanf("%d%d", & n, & m);    for (int i = 1; i <= n; ++ i)    {        scanf("%d", & v);        link(e1, v, i);    }    for (int i = 1; i <= m; ++ i)    {        scanf("%d%d%d", & v, & a, & b);        link(e2, v, i), x[adj] = a, y[adj] = b;    }    for (int i = 1000; i >= 0; -- i)    {        for (int e = e1[i]; e; e = next[e])        {            u = to[e], ufs[u] = u, size[u] = 1;            if (v = find(u - 1))                ufs[v] = u, size[u] += size[v], alter(size[v], - 1);            if (v = find(u + 1))                ufs[v] = u, size[u] += size[v], alter(size[v], - 1);            alter(size[u], 1);        }        for (int e = e2[i]; e; e = next[e])            ans[to[e]] = (query(x[e] - 1) - query(y[e])) >> 1;    }    for (int i = 1; i <= m; ++ i)        printf("%I64d\n", ans[i]);        return 0;}