2025-10-12?
ICPC EC 区域赛成都站 F

先用 lagrange 乘数法解决 给定分组情况(告诉你每个组里面的元素个数 和 s 总和)后的答案求解问题。如果你最终分成了 t 组,每组 \(s_i\) 的综合和物品的数量为 \(\texttt{sum_1,cnt_1..sum_t,cnt_t}\),lagrange 乘数法给出答案就是 \(\sum\limits_{i=1}^t \sqrt{\texttt{sum_i}\times\texttt{cnt_i}}\)
因为题面没有写恰好 M 类,但是我们可以把恰好扩写到题面里面,恰好 M 类对应 wqs 二分。那么我们写一个 wqs 二分,dp 使用二分栈来实现决策单调性的转移即可。做完了。
首先考虑:如果某个组的 \(s_{i,\min}< s_{j,\max}\) 那么我们将这两个物品交换,答案肯定更优。因为根号运算有凹凸性保证。那么我们可以把所有物品按照 \(s\) 排序,设 \(f_i,g_i\) 前 \(i\) 数划分若干组,拼上 wqs 二分赋予的 reward/penalty 后最大的收益是多少,\(g_i\) 表示该划分方案对应的段数。
容易写出转移方程,容易验证该转移方程满足四边形不等式。很难评价的一道复习题。这题好像是之前在读别人游记的时候读到 wqs 二分了,没读到 wqs 二分感觉大概率是想不出来的。不过知道是 wqs 二分之后后面就都我自己玩的,挺有意思。
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;++i)
const int N = 200000 + 10;
int n, m;
int s[N];
double sumv[N];
double dp[N];
int tim[N];
inline double cost(int i, int j, double pnl){
// i < j
return dp[i] + sqrt(double(j - i)) * sqrt(sumv[j] - sumv[i]) + pnl;
}
struct Node {
int idx; // decision point i
int l; // valid interval left
int r; // valid interval right
};
inline bool check(double pnl){
// initialize
for(int i=0;i<=n;i++){ dp[i]=1e100; tim[i]=n+5; }
dp[0] = 0.0;
tim[0] = 0;
deque<Node> dq;
// initial decision 0 is optimal for j in [1..n]
dq.push_back({0, 1, n});
for(int j=1;j<=n;j++){
// pop front decisions whose interval ended before j
while(!dq.empty() && dq.front().r < j) dq.pop_front();
if(dq.empty()){
// shouldn't normally happen if logic correct, but safe-guard:
dp[j] = 1e100;
tim[j] = n+5;
} else {
int i = dq.front().idx;
dp[j] = cost(i, j, pnl);
tim[j] = tim[i] + 1;
}
// we don't need to insert decision for j if j==n (no future j)
if(j == n) continue;
// create new candidate from decision j, valid initially on [j+1, n]
Node cur = {j, j+1, n};
// maintain deque's back: find first position where new decision becomes better
while(!dq.empty()){
Node last = dq.back();
int i_prev = last.idx;
int L = last.l, R = last.r;
// find smallest pos in [L,R] such that cost(j,pos) <= cost(i_prev,pos)
int pos = R + 1;
int lo = L, hi = R;
while(lo <= hi){
int mid = (lo + hi) >> 1;
double c_new = cost(j, mid, pnl);
double c_old = cost(i_prev, mid, pnl);
if(c_new <= c_old){
pos = mid;
hi = mid - 1;
} else lo = mid + 1;
}
if(pos <= last.l){
// new decision is no worse than last on entire last interval -> pop last
dq.pop_back();
} else {
// new decision only wins from pos..R, so shrink last.r and set cur.l = pos
dq.back().r = pos - 1;
cur.l = pos;
break;
}
}
// if deque became empty, cur.l remains j+1 (or set correctly)
if(cur.l <= cur.r) dq.push_back(cur);
// else if cur.l > cur.r then new decision never wins in future range -> do not push
}
return tim[n] <= m;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
double l = 0, r = 0, ans = 0;
for(int i=1;i<=n;i++){
cin >> s[i];
r += sqrt((double)s[i]);
}
sort(s+1, s+n+1);
sumv[0] = 0.0;
for(int i=1;i<=n;i++) sumv[i] = sumv[i-1] + (double)s[i];
r -= sqrt(sumv[n]);
// binary search on penalty
while(r - l > 1e-10){
double mid = (l + r) / 2.0;
if(check(mid)){
ans = mid;
r = mid;
} else l = mid;
}
// compute final dp with best penalty ans
check(ans);
cout << fixed << setprecision(10) << dp[n] - ans * m << "\n";
return 0;
}
由于一些原因我确实不想写二分栈了,这份代码的二分栈部分都是 GPT5 生成的……vp 的时候最后五分钟写了一个对的分治一直在 TLE,很烦。

浙公网安备 33010602011771号