Array——HDU7020
Array
HDU7020
题目大意
给出长度为 \(n\)的序列 \(a\),求出有多少个区间满足区间众数出现的次数大于其他数出现的次数之和。
解题思路
我们将整个序列中的不同数处理出来分别考虑,同时记录每个数出现的位置。
当我们考虑数 \(x\) 时,我们将原序列做一些变动,将原序列中为 \(x\)的数置为 \(1\),非\(x\)数置为 \(-1\),将此序列记为 \(d\),计算出前缀和 \(sum_i = \sum_{j = 1}^id_j\),这时当前位置\(i\)的贡献为 \(G(i) = \sum_{i = 0}^{sum[i]-1} c_i\),(\(c_i\)表示\(i\)出现的次数),\(c_i\)我们可以用树状数组(差分)的形式记录,如果 \(x\)出现了\(k\)次,则我们可以将原序列分为\(k+1\)个区间(即以\(x\)划分区间)。
如当序列为 \(\{1\ 1\ 2\ 2\ 2\ 1\ 1\ \}\),当\(x = 1\)时,序列化为 \(\{1\},\{ 1\ 2\ 2\ 2\},\{1\},\{1\}\)。当考虑同一段时,因为这一区间的前缀和是递减的,即连续的,所以我们可以直接用是树状数组的求和类似前缀和的方式求出这一段的贡献。具体的当某一段区间为的前缀和为 \(x ~ y\) 时,我们可以直接计算 \(getsum(y-1) - getsum(x-2)\),因为众数出现次数要严格大于其他数的出现次数所以当前缀和为\(y\)时,我们是求的 \(\sum_{i = 1}^{y - 1}\),接下来讨论\(getsum(x)\)的求法。注意此时 \(x~y\)对答案的贡献是这一段每个数对答案贡献的和。
而\(\sum_{i = 1}^{x}\sum_{j = 1}^{i}c_j\)是前缀和为\(x\)对答案的贡献,\(getsum(x)\)还需要将\(1~x\)的贡献求前缀和,即前面还有一位求和:\(getsum(x) = \sum_{i = 1}^x\sum_{j = 1}^i\sum_{k = 1}^{j}c_k\)。
上式通过化简:\(getsum(x) = \{c_1\} + \{c_1 + c_1 + c_2\} + \{c_1 + c_1 + c_2 + c_1 + c_2 + c_3\} + \cdots\)
观察可得出:\(getsum(x) = \sum_{i = 1}^xc_i * (1 + 2 + \cdots + (x - i + 1))\)
化简得:\(getsum(x) = \sum_{i = 1} ^ x c_i * \frac{(x-i+1)*(x-i + 2)}2 = \sum_{i = 1}^x \frac{(x+2)(x+1)}2 * c_i - \frac{2x+3}2 * i * c_i + \frac12 * i * i * c_i\)
因此我们可以开三个树状数组分别维护 \(c_i、i * c_i、i * i * c_i\)。
这里仍需要注意的一点是:上式中的 \(1~x\)是\(d\)序列的前缀和,即我们树状数组的下标是前缀和,但是前缀和可能出现负数,所以我们需要把它们映射到正数值域上,并且 \(n\)最大是\(1e6\),所以我们只需要给每个前缀和加上\(1e6+1\)即可。
下面代码中的是一些基础实现,大佬可略过
当考虑\(x\)并在统计前缀和时,我们需要从小到大扫一遍起出现的位置,并考虑其分成的区间段,假设我们当前考虑的区间两端真实下标为\(i,j\),位置\(i\)和位置\(j+1\)为当前枚举的数\(x\),\(cnt\)为前面出现的\(x\)的次数,则两端的前缀和分别为 \(\{2*cnt - i + m\}\) 和 \(\{2*cnt - j + m\}\)(\(m\)为偏移量),因为前缀和的计算就是 \(x\)出现的次数减去其他数出现的次数,而 \(x\)出现的次数为 \(cnt\),假设当前位置为 \(i\),则其他数出现的次数为 \(i - cnt\),所以前缀和即为:\(cnt - (i-cnt) = 2*cnt-i\)
Code
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
#define V vector
using namespace std;
const int N = 2e6 + 7, m = 1e6+1;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
V<int>v[N];
ll t1[N],t2[N],t3[N];
inline void add(int x,ll d){
ll k = x;
while(x < N){
t1[x] += d;
t2[x] += d*k;
t3[x] += d*k*k;
x += x&-x;
}
}
inline ll getsum(int x){
ll ret = 0;
ll k = x;
while(x){
ret += t1[x] * (k + 2) * (k + 1) - t2[x] * (2 * k + 3) + t3[x];
x -= x&-x;
}
return ret/2;
}
void solve(){
int n;
cin >> n;
unordered_set<int>s;
for(int i = 1; i <= n; i ++){
int x;
cin >> x;
s.insert(x);
v[x].pb(i);
}
ll ans = 0;
for(int i : s){
int pre = 0,cnt = 0;
v[i].pb(n+1);
for(int j : v[i]){
int r = 2 * cnt - pre + m, l = 2 * cnt - (j - 1) + m;
ans += getsum(r-1) - getsum(l-2);
add(l,1);
add(r+1,-1);
cnt++;
pre = j;
}
pre = cnt = 0;
for(int j : v[i]){
int r = 2 * cnt - pre + m, l = 2 * cnt - (j - 1) + m;
add(l,-1);
add(r+1,1);
cnt++;
pre = j;
}
v[i].clear();
}
for(int i : s){
v[i].clear();
}
cout << ans << "\n";
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
qc;
int T = 1;
cin >> T;
while(T--){
solve();
}
return 0;
}