P5336 [THUSC2016]成绩单

发布时间 2023-05-01 21:55:50作者: 腾云今天首飞了吗

题意:

期末考试结束了,班主任 L 老师要将成绩单分发到每位同学手中。L 老师共有 \(n\) 份成绩单,按照编号从 \(1\)\(n\) 的顺序叠放在桌子上,其中编号为 \(i\) 的的成绩单分数为 \(W_i\)
成绩单是按照批次发放的。发放成绩单时,L 老师会从当前的一叠成绩单中抽取连续的一段,让这些同学来领取自己的成绩单。当这批同学领取完毕后,L 老师再从剩余的成绩单中抽取连续的一段,供下一批同学领取。经过若干批次的领取后,成绩单将被全部发放到同学手中。
然而,分发成绩单是一件令人头痛的事情,一方面要照顾同学们的心理情绪,不能让分数相差太远的同学在同一批领取成绩单;另一方面要考虑时间成本,尽量减少领取成绩单的批次数。对于一个分发成绩单的方案,我们定义其代价为:
\(a \times k + b \times \sum_{i = 1} ^ k (max_i - min_i) ^ 2\)
其中 \(k\) 是分发的批次数,对于第 \(i\) 披分发的成绩单,\(max_i\) 是最高分数,\(min_i\) 是最低分数,\(a\)\(b\) 是给定的评估参数。现在,请你帮助 L 老师找到代价最小的分发成绩单的方案,并将这个最小的代价告诉 L 老师。当然,分发成绩单的批次数 \(k\) 是你决定的。

分析:

这个题属实有些抽象,卡了好久

显然,这个题涉及到区间合并,与之类似的还有P2135 方块消除 这道题。一般涉及到区间合并,就要求对该区间以后的区间记录一定的信息,一般在状态设计上处理。这样的模型称为挂载模型

那么,对于这道题来说,除了区间的左右端点,影响该区间的代价还有区间内的最大最小值。将四个要素塞入状态中,定义状态 \(dp[l][r][mn][mx]\) 表示把区间 \([l,r]\) 和接在这个区间后面的,满足这个区间里的最大值为 \(mx\),最小值为 \(mn\) 的另一个区间内的成绩单全部发放完毕,所需要的最小代价。也就是说,这个状态定义的是前后两个区间拼起来的总区间。

这样的状态设计就有点绕,想起来就更抽象了。不过它却是符合挂载模型的思维方法的,令人感慨。另一个奇怪的点是,如果你要表示分发位置为 \(i\) 的成绩单所需要的代价,其状态就是 \(dp[i][i - 1][w[i]][w[i]]\)。区间的左端点虽然大于了右端点,但是无伤大雅,说明前面一个区间是不存在的,而后面那个区间的最小值等于最大值,都为 \(w[i]\),于是就表示出了分发位置为 \(i\) 的成绩单所需要的代价。

这个题的预处理也比较奇怪,不过比较类似上一段的叙述。
根据上面的想法,用 \(dp[i][i - 1][mn][mx]\) 就可以表示 \(i\) 位置以后的一个区间最大值为 \(mx\),区间最小值为 \(mn\) 的一个区间。因此预处理如下:

\[dp[i][i - 1][mn][mx] = a + b * (mx - mn) * (mn - mx) \]

接下来说说怎么转移。
对于区间 \([l,r]\),状态 \(dp[l][r][mn][mx]\) 来说,你可以选择直接把区间 \([l,r]\) 作为一批来发放,再把后面一个满足区间最大值为 \(mx\),区间最小值为 \(mn\) 的区间作为另一个批次,两个批次分别发放;也可以将这个区间和后面一个满足区间最大值为 \(mx\),区间最小值为 \(mn\) 的区间并起来作为一个批次一起发放;还可以在区间 \([l,r]\) 中枚举一个 \(k\),把区间 \([k + 1,r]\) 单独作一个批次分发,把区间 \([l,k]\) 与后面一个满足区间最大值为 \(mx\),区间最小值为 \(mn\) 的区间并起来作为一个批次分发。
上面三种选择分别对应了如下方程:

\[dp[l][r - 1][w[r]][w[r]] + a + b * (mn - mx) * (mn - mx) \]

\[dp[l][r - 1][mx][mn] \]

\[dp[l][k][mx][mn] + dp[k + 1][r - 1][w[r]][w[r]],l <= k < r \]

\(dp[l][r][mn][mx]\) 在上面三种转移中取一个 \(min\) 即可。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 50;
int dp[MAXN + 5][MAXN + 5][MAXN + 5][MAXN + 5],n,A,B,w[MAXN + 5];
vector<int> lsh;
int main(){
    memset(dp,0x3f,sizeof dp);
    scanf("%d%d%d",&n,&A,&B);
    for(int i = 1; i <= n; i++){
        scanf("%d",&w[i]);
        lsh.push_back(w[i]);
    }
    sort(lsh.begin(),lsh.end());
    lsh.erase(unique(lsh.begin(),lsh.end()),lsh.end());
    for(int i = 1; i <= n; i++){
        w[i] = lower_bound(lsh.begin(),lsh.end(),w[i]) - lsh.begin() + 1;                            
    }
    for(int i = 1; i <= n; i++){
        for(int x = 1; x <= lsh.size(); x++){
            for(int y = x; y <= lsh.size(); y++){
                dp[i][i - 1][x][y] = A + B * (lsh[x - 1] - lsh[y - 1]) * (lsh[x - 1] - lsh[y - 1]);
            }
        }
    }
    for(int len = 1; len <= n; len++){
        for(int l = 1; l + len - 1 <= n; l++){
            int r = l + len - 1;
            for(int mi = 1; mi <= lsh.size(); mi++){
                for(int mx = mi; mx <= lsh.size(); mx++){
                    dp[l][r][mi][mx] = min(dp[l][r][mi][mx],dp[l][r - 1][w[r]][w[r]] + A + B * (lsh[mx - 1] - lsh[mi - 1]) * (lsh[mx - 1] - lsh[mi - 1]));
                    dp[l][r][mi][mx] = min(dp[l][r][mi][mx],dp[l][r - 1][min(mi,w[r])][max(mx,w[r])]);
                    for(int k = l; k < r; k++){
                        dp[l][r][mi][mx] = min(dp[l][r][mi][mx],dp[l][k][mi][mx] + dp[k + 1][r - 1][w[r]][w[r]]);
                    }
                }
            }
           
        }
    }
    cout << dp[1][n - 1][w[n]][w[n]] << "\n";
    
}