题解 CF212D 【Cutting a Fence】

青鸟_Blue_Bird

2020-06-25 00:16:43

Solution

#### [AC传送门!](https://www.luogu.com.cn/problem/CF212D) 看题目,首先肯定是要先想想我们应该用什么处理方法。1e6的询问题,我们肯定是不能在线处理的,因此先确定: #### 先尽可能预处理出最终答案,然后输出 于是乎,开始动手吧: 根据题目中f的定义,我们要找到许多区间中的最小值。那么,假设我们的一个数 $a_x$ (1≤x≤n),如何找到一个区间使得其最小值为$a_x$呢? 并不难,只要我们找到了它最左边的第一个小于它的数$a_l$,以及它右边第一个小于等于它的数$a_r$,那么对于区间[l+1, r-1],其最小值就是$a_x$。 即: 对于一个数$a_x$,其作为最小值得区间为 [l + 1 , r - 1],(其中$a_l$ < $a_x$ 且 $a_r$ < $a_x$, l,r均为区间 [l , x]与 [x , r]中的最小值) 那么,我们设变量 L 为左区间 [l + 1, x] 的长度, R 为右区间 [x, r - 1] 的长度。 我们~~强制~~假设L ≤ R ### 那么,对情况进行分类讨论: 为了方便,我们把“有x个区间符合要求” 暂时定义为 “有x个区间满足有$min_x$ = $a_x$" 1、如果 k $\in$ [1 , L] 那么就会有 k 个区间符合要求 2、如果 k $\in$ [L + 1, R] ,那么会有 L 个区间符合要求。 3、如果 k $\in$ [R + 1, L + R - 1] 那么会有(L + R - K)个区间满足要求。 那么,对于一个数对应的贡献 $sum_x$ ,对应有: 1、$sum_x$ = $sum_x$ + k * $a_x$ 2、$sum_x$ = $sum_x$ + L * $a_x$ 3、$sum_x$ = $sum_x$ + $a_x$ * (L + R) - k * $a_x$ 观察以上~~柿子~~,不难发现,他们都是: $a_x$ * k + B 的格式。也就是说,除了变量K,剩下的都是定值。 #### 那么,我们为什么不快速求出每一个~~柿子~~的 B 呢? 于是,我们改变一下,以 K 参考系: 设$S_1$ = $\sum_{}a_x$(满足上述条件) -> 即为上文所述的所要乘上k的 $a_x$ 的和。 $S_2$ = $\sum_{}^{}n$($n$为任意实数) -> 即为上文所述的B 对于一个数K,$ans_k$ = ($S_1$ * k + $S_2$) / (n - k + 1) 具体的例子,我觉得大家可以参考[PinkCloud大佬的博客](https://www.luogu.com.cn/blog/LuckyCloud/solution-cf212d): 那么,我们如何实现快速求和呢? 对$S_1$和$S_2$数组分别进行差分即可。 具体实现还是看代码吧: ```cpp #include <bits/stdc++.h> using namespace std; #define N 1000010 #define ll long long inline ll read(){ ll x = 0, s = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-')s = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); } return x * s; } ll n, m, r[N], l[N], a[N]; ll s1[N], s2[N]; double ans[N]; void search(){ for(int i = n; i; i--){ /*向右找到第一个小于等于这个数的位置*/ if(a[i + 1] <= a[i]) r[i] = i + 1; else for(int j = r[i + 1]; j <= n + 1; j = r[j]) /*一直向右跑*/ if(a[j] <= a[i]){ r[i] = j; break; } } for(int i = 1;i <= n; i++){ /*同上,向左找*/ if(a[i - 1] < a[i]) l[i] = i - 1; else for(int j = l[i - 1]; j >= 0; j = l[j]) if(a[j] < a[i]) { l[i] = j; break; } } for(int i = 1;i <= n; i++){ /*换算成距离 L 和 R*/ l[i] = i - l[i]; r[i] = r[i] - i; } return ; } void prepare(){ /*进行差分以及预处理答案*/ for(int i = 1;i <= n; i++){ int x = l[i], y = r[i]; if(x > y) swap(x, y); /*保持 L < S*/ s1[1] += a[i]; s1[x + 1] -= a[i]; s2[x + 1] += a[i] * x; s1[y + 1] -= a[i]; s2[y + 1] += a[i] * y; s1[x + y] += a[i]; s2[x + y] -= a[i] * (x + y); } ll num1 = 0, num2 = 0; for(int i = 1;i <= n; i++){ num1 += s1[i], num2 += s2[i]; ans[i] = ((num1 * i + num2) * 1.00) / ((n - i + 1) * 1.00); /*预处理出所有答案*/ } return ; } int main(){ n = read(); for(int i = 1;i <= n; i++) a[i] = read(); a[n + 1] = (ll)(-(1 << 50));/*两端插入极小值,避免下一步越界或者出现错误*/ a[0] = (ll)(-(1 << 50)); search(); /*先搜索距离*/ prepare(); /*然后处理答案*/ m = read(); while(m--){ ll k = read(); printf("%.9lf\n", ans[k]); } return 0; } ```