题解:P12644 [KOI 2024 Round 1] 会议地点

· · 题解

题目传送门。

思路:

首先想到直接暴力,时间复杂度 O(QN^2 \cdot N!)
所以,我们的目标是:把时间复杂度降到 O(Q),也就是单次询问时间复杂度 O(1)

问题一:单次会议的开销

让我们看一个借来的公式:\displaystyle\sum^{r}_{i = l}\vert X_a - X_i \vert
如果去掉绝对值,想必大家都能成功化简,但问题是:怎么去掉绝对值。
注意到题面说“人家的坐标按从小到大的顺序排列”,所以上式可以被拆成两个部分:\displaystyle\sum^{a-1}_{i = l}(X_a - X_i)\displaystyle\sum^{r}_{i = a+1}(X_i - X_a)
化解得 \displaystyle(a - l) \cdot X_a - \sum_{i=l}^{a-1}X_i + \sum_{i=a+1}^{r}X_i - (r - a) \cdot X_a
求和部分可以用前缀和来做,整个式子时间复杂度 O(1)
这样,时间复杂度就降为了 O(QN \cdot N!)

问题二:会议的顺序

由于疲劳度关系到相邻的两个点,所以我们可以画图表示。
接下来我会用题面中的例子来做示范:
若会议召开顺序为 3 \rightarrow 10 \rightarrow 11,画图为:

这时我们注意到,这条线折返了一下,这个折返是无用的。
怎么让它不折返呢?我们只需要使召开顺序从小到大从大到小排序就行了。
像这样:

这样我们就不用枚举所有排序了,时间复杂度 O(QN)

问题三:计算最小疲劳度

观察问题二中的图,我们发现一次会议集合的最小疲劳度是最大开销减最小开销
那如何计算最大开销与最小开销呢?
对于一个下标为 a 的开会地点,设其右边有 x 户人家,左边有 y 户人家,向右移动一户人家会移动 u 个单位,向左移动一户人家会移动 v 个单位。

$a$ 每向左移动一次,开销会增加 $(y - 1 - x) \cdot u$。 随便选一个点,每次在下面三种行动方案中选一种开销最小的一种执行,观察 $a$ 的变化。 + 不动。 + 向左移动一次。 + 向右移动一次。 我们发现,$a$ 最后总是会停在正中间,这就证明,在正中间的人家开会,开销最小。 再做类似的实验,不过,这次找开销最大的人家。 我们又发现,$a$ 最后总是会停在最外侧,这就证明,在最外侧的人家开会,开销最大。 这样我们就可以只计算三户人家了,时间复杂度终于来到了 $O(Q)$。 ### 代码 给代码之前,还有一件事要说:题面读入的是坐标,我们要的是下标,所以我们要二分查找下标。 上代码: ``` #include<bits/stdc++.h> #define FOR(i,a,b) for(int i=a;i<=b;i++) using namespace std; long long n,z[200009],q,h[200009],l,r;//不开 long long 见祖宗 long long hy(long long a){ return (z[a]*(a-l)-(h[a-1]-h[l-1]))+((h[r]-h[a])-z[a]*(r-a)); } int main(){ cin>>n>>q; FOR(i,1,n){ cin>>z[i]; h[i]=h[i-1]+z[i]; } while(q--){ int x,y; cin>>x>>y; l=lower_bound(z+1,z+1+n,x)-z; //lower_bound找第一个大于等于x的数,返回地址,-z获得下标 r=upper_bound(z+1,z+1+n,y)-z-1; //upper_bound找第一个大于x的数,-1后就是最后一个小于等于x的数 if(l<=0||l>n||r<=0||r>n||l>=r){ //一定要有,判断区间里是否有人家 cout<<"0\n"; continue; } long long maxx=max(hy(l),hy(r)); long long minn=hy(l+(r-l)/2); cout<<maxx-minn<<"\n"; } } ``` 看在我写了三个小时的份上,点个赞再走呗!