THUSC2016 成绩单 和 LOJ3226 Greedy Pie Eaters
成绩单
期末考试结束了,班主任 L 老师要将成绩单分发到每位同学手中。L老师共有 \(n\) 份成绩单,按照编号从 \(1\) 到 \(n\) 的顺序叠放在桌子上,其中编号为 \(i\) 的成绩单分数为 \(W_i\)。
成绩单是按照批次发放的。发放成绩单时,L 老师会从当前的一叠成绩单中抽取连续的一段,让这些同学来领取自己的成绩单。当这批同学领取完毕后,L 老师再从剩余的成绩单中抽取连续的一段,供下一批同学领取。经过若干批次的领取后,成绩单将被全部发放到同学手中。
然而,分发成绩单是一件令人头痛的事情,一方面要照顾同学们的心理情绪,不能让分数相差太远的同学在同一批领取成绩单;另一方面要考虑时间成本,尽量减少领取成绩单的批次数。对于一个分发成绩单的方案,我们定义其代价为:
其中 \(k\) 是分发的批次数,对于第 \(i\) 批分发的成绩单,\(\text{max}_i\) 是最高分数,\(\text{min}_i\) 是最低分数,\(a\) 和 \(b\)是给定的评估参数。
现在,请你帮助 L 老师找到代价最小的分发成绩单的方案,并将这个最小的代价告诉 L 老师。当然,分发成绩单的批次数 \(k\) 是由你决定的。
\(n \leq 50, a \leq 1500, b \leq 10, w_i \leq 1000\)
题解
https://blog.csdn.net/qq_39677783/article/details/86898654
考虑到这题的抽取方式:每次从中间抽取一段,然后两边的又会拼起来,所以区别于序列划分类问题,前缀DP的状态不够清晰无法转移,可以想到区间DP。
先设\(g(i,j)\)表示将\([i,j]\)这段区间的数全部消掉的最小代价,那么最终答案就是\(g(1,n)\)。但是这样会有问题:我们需要知道每次消去的\(\max_i\)和\(\min_i\)是多少,才能方便转移。那么我们可以这样做:首先对\(w_i\)离散化,假设\(tmp_i\)表示原数组\(w\)中第\(i\)小的数;对于一个\(g(i,j)\),我们可以枚举值域区间\([l,r]\),表示将\([i,j]\)这段区间里的数全部消掉之前最后一次消去的数都在值域范围\([l,r]\)当中。那么最后一次消去显然会产生\(a+b\times(tmp_r-tmp_l)^2\)的代价。那么接下来的问题就是:将区间\([i,j]\)中的数消到只剩下值域范围在\([l,r]\)中的数,最小代价是多少,不妨设其为\(f(i,j,l,r)\)。
这样一来我们\(g(i,j)\)的转移方程就有了:
下面考虑\(f(i,j,l,r)\)的转移。一个显然的思路是
然而很不幸这样做会漏掉一些情况。
正确的做法是:首先我们可以找到\([i,j]\)区间里左边第一个不在值域范围\([l,r]\)中的数的位置\(p\),以及右边第一个不在值域范围\([l,r]\)中的数的位置\(q\),如果存在这样的区间\([p,q]\)(如果不存在当然就不用管了),那么
好了,这两个转移方程出来之后,剩下的就是区间DP套路了。当然是枚举区间长度再枚举左端点做DP就行了。
时间复杂度\(O(n^5)\)
CO int N=60,inf=1e9;
int w[N],tmp[N];
int f[N][N][N][N],g[N][N];
int main(){
int n=read<int>(),a=read<int>(),b=read<int>();
for(int i=1;i<=n;++i) read(w[i]);
copy(w+1,w+n+1,tmp+1);
sort(tmp+1,tmp+n+1);
int all=unique(tmp+1,tmp+n+1)-tmp-1;
for(int i=1;i<=n;++i)
w[i]=lower_bound(tmp+1,tmp+all+1,w[i])-tmp;
for(int i=1;i<=n;++i){
g[i][i]=a;
for(int l=1;l<=all;++l)for(int r=l;r<=all;++r)
if(w[i]<l or w[i]>r) f[i][i][l][r]=a;
}
for(int len=2;len<=n;++len)for(int i=1,j=i+len-1;j<=n;++i,++j){
g[i][j]=inf;
for(int l=1;l<=all;++l)for(int r=l;r<=all;++r){
int p=0,q=0;
for(int t=i;t<=j;++t)
if(w[t]<l or w[t]>r) {p=t; break;}
for(int t=j;t>=i;--t)
if(w[t]<l or w[t]>r) {q=t; break;}
if(p and q) f[i][j][l][r]=g[p][q];
else f[i][j][l][r]=inf;
for(int k=i;k<j;++k)
f[i][j][l][r]=min(f[i][j][l][r],f[i][k][l][r]+f[k+1][j][l][r]);
g[i][j]=min(g[i][j],f[i][j][l][r]+a+b*(tmp[r]-tmp[l])*(tmp[r]-tmp[l]));
}
}
printf("%d\n",g[1][n]);
return 0;
}
Greedy Pie Eaters
Farmer John 有 \(M\) 头奶牛,为了方便,编号为 \(1\ldots M\)。这些奶牛平时都吃青草,但是喜欢偶尔换换口味。Farmer John 一天烤了 \(N\) 个派请奶牛吃,这 \(N\) 个派编号为 \(1\ldots N\)。第 \(i\) 头奶牛喜欢吃编号在 \([l_i,r_i]\) 中的派(包括两端),并且没有两头奶牛喜欢吃相同范围的派。第 \(i\) 头奶牛有一个体重 \(w_i\),这是一个在 \([1, 10^6]\) 中的正整数。
Farmer John 可以选择一个奶牛序列 \(c_1,c_2,\ldots c_K\),并让这些奶牛按这个顺序轮流吃派。不幸的是,这些奶牛不知道分享!当奶牛 \(c_i\) 吃派时,她会把她喜欢吃的派都吃掉——也就是说,她会吃掉编号在 \([l_{c_i},r_{c_i}]\) 中所有剩余的派。Farmer John 想要避免当轮到一头奶牛吃派时,她所有喜欢的派在之前都被吃掉了这样尴尬的情况。因此,他想让你计算,要使奶牛按 \(c_1,c_2,\ldots c_K\) 的顺序吃派,轮到这头奶牛时她喜欢的派至少剩余一个的情况下,这些奶牛的最大可能体重(\(w_{c_1}+w_{c_2}+\ldots +w_{c_K}\))是多少。
对于全部数据,\(1\le N\le 300,1\le M\le \frac{N(N-1)}{2},1\le l_i\le r_i\le N,1\le w_i\le 10^6\)。
题解
http://jklover.hs-blog.cf/2020/06/07/Loj-3226-Greedy-Pie-Eaters/#more
首先可以假定所有\(\binom{n}{2}\)个区间都对应了一头牛,没有给出的可以将其牛的重量看作 0 .
当某头牛要吃区间 \([l,r]\) 时,若剩下的派大于 \(1\) ,可以先让其他牛吃到还剩一个,于是每头牛都恰好吃掉一个派.
设 \(f(l,r)\) 表示把 \(l,r\) 内的派吃完能获得的最大收益,转移时枚举最后被吃的派是 \(k\) ,
其中 \(g(l,r,k)=\max_{l\le x\le k\le y \le r} A_{x,y}\) ,表示用区间不超出 \([l,r]\) 的牛吃掉第 \(k\) 个派能获得的最大收益.
转移时考虑 \([l,r]\) 这个区间的贡献就行了.
时间复杂度 \(O(n^3)\) .
CO int N=310;
int a[N][N],g[N][N][N],f[N][N];
int main(){
int n=read<int>();
for(int m=read<int>();m--;){
int w=read<int>(),l=read<int>(),r=read<int>();
a[l][r]=w;
}
for(int len=1;len<=n;++len)
for(int l=1,r=l+len-1;r<=n;++l,++r)
for(int k=l;k<=r;++k){
g[l][r][k]=max(a[l][r],max(g[l+1][r][k],g[l][r-1][k]));
f[l][r]=max(f[l][r],f[l][k-1]+f[k+1][r]+g[l][r][k]);
}
printf("%d\n",f[1][n]);
return 0;
}