#41. 新斯诺克
Description
内部题目,不放链接了。
Solution
题意大概就是让求平均数大于 \(m\) 的子段的个数。
很容易想到前缀和,但是这个前缀和该怎么用呢?
我们可以先把 \(a\) 序列每个数都减去 \(m\) 之后在做前缀和。
这样判断一个子段是否合法只需要判断子段和是否大于 0,即可。
暴力枚举的话是 \(O(n^2)\) 的,无法通过此题。
仔细分析我们需要的条件 \(sum_i - sum_j > 0 \ (i > j)\)。
诶,这不就是个二维正序嘛。
于是拿个树状数组维护即可。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
const ll N = 1e6 + 10;
ll n, m, ans;
ll c[N];
struct node{
ll a, b;
bool operator < (const node &t) const{
return a != t.a ? a < t.a : b > t.b;
}
}p[N];
inline void update(ll x, ll y){
for(; x <= n; x += (x & -x))
c[x] += y;
}
inline ll query(ll x){
ll res = 0;
for(; x > 0; x -= (x & -x))
res += c[x];
return res;
}
signed main(){
scanf("%lld%lld", &n, &m);
for(ll i = 1; i <= n; i++){
scanf("%lld", &p[i].a);
p[i].a = p[i - 1].a + p[i].a - m;
ans += (p[i].a > 0);
p[i].b = i;
}
sort(p + 1, p + 1 + n);
for(ll i = 1; i <= n; i++){
update(p[i].b, 1);
ans += query(p[i].b - 1);
}
printf("%lld\n", ans);
return 0;
}