题意:
链接:https://ac.nowcoder.com/acm/contest/889/H
给你 n 棵树每一棵树的高度,每一棵树从左到右编号为 1, 2, ... n 。Q 次询问,每次询问(l, r, x, y)代表在编号为 [l, r] 这个闭区间内的树,我需要砍 y 次把这些树砍成高度为 0 ,但是每次砍只能规定一个高度,这个高度以上的需要全部砍去,下面的不动,且每次砍的高度之和需要相同, 比如 高度为 [6,2,1]的三棵树 我规定 y = 3,那么第一次规定砍的高度为3,之后砍成 [3,2,1] ,第二次规定砍的高度为1,之后砍成[1,1,1],第三次规定砍的高度为0,之后砍成 [0, 0, 0] 。那么x就是询问砍第x次时候需要规定的高度。每次询问都是独立的,树木还是最初没有被砍过的树木。
解题思路:
对于区间[l, r]的树木,首先我可以使用前缀和求出[l, r] 所有树木的高度之和 sum,然后除以需要砍的次数 y。就可以求出来每次需要砍掉的高度,用每次需要砍掉的高度乘以 x ,就是总的需要砍掉的树木的高度和。那么现在问题就转化为:求 ans 使得 ans 以上的树木的高度和为 sum / y * x 。那么我们只需要二分这个ans,每次求一个被砍去的树的高度和与 sum / y * x 比较就可以。现在问题是给你一个高度ans怎么求出在[l, r]之间树木在ans以上的高度的和。就可以使用主席树来维护每种树的个数。到时候只需要在区间 [l, r] 之间查找比 ans 高的树木有多少颗 (假设为 cut_num 棵),还有比ans高的树木的总高度(假设为 cut_sum ),然后用 cut_sum - cut_num * ans 这个就是在[l, r]之间树木在ans以上的高度的和。具体细节可以参考AC代码。
AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug prllf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 2e5 + 7;
const double pi = acos(-1);
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
ll ans;
ll n, q, l, r, x, y, cnt, cut_num, cut_sum, len;
ll sum[maxn], h[maxn], root[maxn];
struct node
{
ll l, r, num, sum;
}T[maxn * 40];
void update(ll l, ll r, ll &x, ll y, ll pos)
{
T[++cnt] = T[y];
T[cnt].num++; // 维护个数
T[cnt].sum += pos; // 维护高度和
x = cnt;
if(l == r) return ;
int mid = (l + r) >> 1;
if(pos <= mid) update(l, mid, T[x].l, T[y].l, pos);
else update(mid + 1, r, T[x].r, T[y].r, pos);
}
void query(ll l, ll r, ll x, ll y, ll pos)
{
if(r < pos) return ;
if(pos <= l) // 找大于等于 mid 的
{
cut_num += T[y].num - T[x].num;
cut_sum += T[y].sum - T[x].sum;
return ;
}
ll mid = (l + r) >> 1;
query(l, mid, T[x].l, T[y].l, pos);
query(mid + 1, r, T[x].r, T[y].r, pos);
}
int main()
{
scanf("%lld %lld", &n, &q);
len = 100000;
for(ll i = 1; i <= n; i++)
{
scanf("%lld",&h[i]);
sum[i] = sum[i - 1] + h[i]; // 求前缀和,为后面计算总的高度和
}
for(ll i = 1; i <= n; i++) update(1, len, root[i], root[i - 1], h[i]); // 建树
while(q--)
{
scanf("%lld %lld %lld %lld", &l, &r, &x, &y);
double S_cut = (sum[r] - sum[l - 1]) * 1.0 * x / y;
// 需要砍掉的面积,先乘后除,减小误差。
ll L = 1, R = 100000;
while(L <= R)
{
ll mid = (L + R) / 2.0;
cut_num = 0; // 大于等于mid的树木的个数
cut_sum = 0; // 大于等于mid的树木的高度和
query(1, len, root[l - 1], root[r], mid);
if( S_cut > 1.0 * (1.0 * cut_sum - 1.0 * (mid - 1) * cut_num) )
// 因为我查找的是大于等于 mid 的树木高度的和,
// 所以我底下没有砍去的部分应该为 (mid - 1) * cut_num。
{
ans = mid;
// 这样二分出来是一个正数,再此高度以上的和其实是小于 S_cut 的,
// 所以答案是在 [ans - 1, ans] 这个范围,具体的范围在后面精确求解。
R = mid - 1; // 砍的不够多,水平降低,多砍一些
}
else
{
L = mid + 1;
}
}
// 开始精确求解,可以画一个图就知道这些公式了,很简单,就是知道面积,
// 知道底,求一个高 。 答案是在 [ans - 1, ans] 这个范围,上面也提到了
cut_num = 0; // 相当于底
cut_sum = 0;
query(1, len, root[l - 1], root[r], ans - 1);
double del = S_cut - 1.0 * (1.0 * cut_sum - 1.0 * ans * cut_num);
// 相当于还需要砍去的树的高度和,相当于面积
double hh = del / cut_num; // 还需要砍去的高
hh = 1.0 - hh; // 剩下的高
printf("%.15f\n", ans * 1.0 - 1.0 + hh);
// ans往下一个高度,然后加上剩下的高就是精确答案了。
}
}