题目链接
给你长为 n n n 的序列 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an 和一个参数 m m m ,删掉其中若干个位置 p 1 , p 2 , ⋯ , p k p_1,p_2,\cdots,p_k p1,p2,⋯,pk ,耗费 ∑ i = 1 k i ⋅ a p i \sum_{i=1}^{k}i\cdot a_{p_i} ∑i=1ki⋅api 的代价。序列被分为 k = 1 k=1 k=1 段,对每段求和,若 s u m k > m sum_k>m sumk>m ,则需额外支付 s u m k sum_k sumk 的代价。求最小总代价。 n ≤ 1 0 5 , 1000 ≤ a i ≤ 2000 n\leq 10^5,1000\leq a_i \leq 2000 n≤105,1000≤ai≤2000 。
首先设状态, f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个位置选了 j j j 个,其中第 i i i 个位置强制被选择。我们在序列的最后加一个 0 0 0,那么答案就是 m a x { f [ n ] [ j ] } max\{f[n][j]\} max{f[n][j]} 。考虑转移,枚举上一个删除的点 k k k:
f [ i ] [ j ] = m i n { d p [ k ] [ j − 1 ] + c a l c ( i − 1 , k ) } + j ⋅ a [ i ] f[i][j]=min\{dp[k][j-1]+calc(i-1,k)\}+j\cdot a[i] f[i][j]=min{dp[k][j−1]+calc(i−1,k)}+j⋅a[i]
其中 c a l c ( i , j ) calc(i,j) calc(i,j) 为从 j + 1 j+1 j+1 到 i i i 花费的代价,设 s [ n ] = ∑ i = 1 n a i s[n]=\sum_{i=1}^{n}a_i s[n]=∑i=1nai 即 a a a 的前缀和:
c a l c ( i , j ) = [ s [ i ] − s [ j ] > m ] ⋅ ( s [ i ] − s [ j ] ) calc(i,j)=[s[i]-s[j] > m]\cdot (s[i]-s[j]) calc(i,j)=[s[i]−s[j]>m]⋅(s[i]−s[j])
对于每个 f [ i ] [ j ] f[i][j] f[i][j] 枚举上一个删除点 k k k ,得到一个 O ( n 3 ) O(n^3) O(n3) 做法。由于第二维的转移只与上一次有关,可以用滚动数组去掉。
注意到删除点 k k k 在某一个位置之前(设此位置为 c u r cur cur), c a l c ( i − 1 , k ) > 0 calc(i-1,k)>0 calc(i−1,k)>0 即 c a l c ( i − 1 , k ) = s [ i − 1 ] − s [ k ] calc(i-1,k)=s[i-1]-s[k] calc(i−1,k)=s[i−1]−s[k],那么 $ f[i]=s[i-1]+min{g[k]-s[k]}$,其中 g [ k ] g[k] g[k] 为上一轮转移的答案即 f [ k ] [ j − 1 ] f[k][j-1] f[k][j−1] 。在每轮转移中,我们更新 c u r cur cur 的位置,并扫过更新的位置记录 { g [ k ] − s [ k ] } \{g[k]-s[k]\} {g[k]−s[k]} 的前缀最小值。这一部分的复杂度被均摊到 O ( 1 ) O(1) O(1) 。如果删除点在 c u r cur cur 之后, c a l c ( i − 1 , k ) = 0 calc(i-1,k)=0 calc(i−1,k)=0, f [ i ] = m i n { g [ k ] } f[i]=min\{g[k]\} f[i]=min{g[k]} ,这一部分可以用单调队列维护:保持队内的元素在 c u r cur cur 之后、 i i i 之前,那么队头的元素即为最小值。这一部分的复杂度也被均摊到 O ( 1 ) O(1) O(1) 。每轮转移的答案就是两部分答案的较小值。记得加上删除当前点的代价 c n t ⋅ a i cnt \cdot a_i cnt⋅ai 。总体复杂度优化到了 O ( n 2 ) O(n^2) O(n2) 。
注意数据范围,如果一个点也不删除,最多付出代价 2000 ⋅ n 2000\cdot n 2000⋅n ;如果删除 k k k 个点,最少付出代价 1000 ⋅ k ( k + 1 ) 2 1000\cdot \frac{k(k+1)}{2} 1000⋅2k(k+1) ,因此 k ≤ 2 n k\leq 2\sqrt n k≤2n 。复杂度优化到了 O ( n n ) O(n\sqrt n) O(nn) 。
#include
using namespace std;
const int MAXN = 1e5 + 10;
const int INF = 998244353;
int n, m;
int a[MAXN], s[MAXN];
int f[2][MAXN]; // 滚动数组
struct node
{
int val;
int id;
};
deque<node> dq;
void push(int val, int id) // 进单调队列
{
while(!dq.empty() && val < dq.back().val) dq.pop_back();
dq.push_back((node){val, id});
}
int front(int cur) //取id在cur以后的队头元素
{
while(dq.front().id < cur) dq.pop_front();
return dq.front().val;
}
void clear()
{
dq.clear();
dq.push_back((node){0, 0});
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
s[i] = s[i-1] + a[i];
}
a[++n] = 0; s[n] = s[n-1];
memset(f, 0x3f, sizeof(f));
f[0][0] = f[1][0] = 0;
int ans = s[n];
int o = 0;
// 预处理出去掉一个的情况
for(int i = 1; i <= n; i++) f[o][i] = s[i-1] > m ? s[i] : a[i];
for(int cnt = 2; cnt <= 152; cnt++) // 经测试,最多转移152次
{
o ^= 1; //滚动
int cur = 0;
clear();
int tmp = INF; // 前缀最小值
for(int i = 1; i <= n; i++)
{
while(s[i-1] - s[cur] > m)
{
tmp = min(tmp, f[o^1][cur] - s[cur]);
cur++;
}
f[o][i] = min(tmp + s[i-1], front(cur));
f[o][i] += cnt * a[i];
push(f[o^1][i], i);
}
ans = min(ans, f[o][n]);
}
printf("%d\n", ans);
return 0;
}