USACO 24 Feb Pt T2 题解

· · 题解

赛时场切了,感觉相当有意思,来补一篇题解。

简要题意

给定一个长度为 n 的序列,有 K 个位置被固定住了,剩下的数之间可以随意交换,问最小的 \sum_{i=1}^{n-1}\max(a_i,a_{i+1}) 的权值是多少。

## 分析性质 考虑将 $\max(a_i,a_{i+1})$ 拆成 $\dfrac{a_i+a_{i+1}+|a_i-a_{i+1}|}{2}$。 其中 $a_i+a_{i+1}$ 是固定的,意思就是我们要最小化 $|a_i-a_{i+1}|$ 的总和。 对于边界的情况,我们可以往两端塞入一个被固定的充分大的数 $C$,最后再减去这部分的答案即可。 由于原来固定的 $K$ 个位置将原序列分为至多 $K+1$ 段,考虑假如我们已经将数分配进了这 $K$ 段里面,段内如何分配才是最优。 由于段与段之间互不影响,我们考虑对于一段计算贡献。 考虑这一个段左边的数为 $L$,右边的数为 $R$,不失一般性地假设 $L\le R$。 那么我们肯定是将分配进来的数从左到右,从小到大排最优,因为可以通过如下的调整法证明: 1. 假如相邻的三个数 $x,y,z$ 满足 $x\ge y \le z$,那么将其变为 $y,x,z$ 不会更劣。 2. 同理,对于 $x\le y \ge z$ 将其变为 $x,z,y$ 不会更劣。 然后一直调整可以使分配的数有序,然后对于边界,我们已经假设 $L\le R$,那么把小的放左边肯定更优。 设这段数的最小值为 $mi$,最大值为 $ma$,那么这一段的贡献是 $|L-mi|+ma-mi+|R-ma|$。 则最后答案为 $$\sum_{i=1}^{K+1}|L_i-mi_i|+ma_i-mi_i+|R_i-ma_i|$$ 答案只和每一段的最小值 $mi_i$ 和 $ma_i$ 有关。 考虑如何分配 $mi_i$ 和 $ma_i$。 **性质:存在一种最优方案,使得对于任意的数对 $(i,j)$,都满足 $(mi_i,ma_i)$ 和 $(mi_j,ma_i)$ 的关系为相离或者包含。** 证明: 还是考虑调整法,不妨设存在 $(i,j)$ 满足 $mi_i\lt mi_j \lt ma_i \lt ma_j$,则我们可以通过交换第 $i$ 段里最大的数和第 $j$ 段里最小的数,直到 $ma'_i\le mi'_j$,此时 $ma'_i \le ma_i$ 且 $mi'_j\ge mi_i$。 考虑 $ma_i$ 变小对第 $i$ 段贡献的影响,由于第 $i$ 段贡献与 $ma_i$ 有关的只有 $ma_i+|ma_i-R|$,分类讨论可得 $ma_i$ 变小后,这个式子要么不变要么变小。 同理 $mi_i$ 变大,对答案的贡献也要么不变要么变小。 所以这样调整会使得答案不劣。 证毕。 ## 做法 从上面的式子我们可以得到一个大概的思路,通过确定每段的边界,然后将剩下的数填进去。 容易发现我们不关系中间的数怎么填的,只需要有一个合法的填数方案即可。 考虑我们从小到大考虑每个数,要么它作为某一段的左边界,要么它填入目前左边界最大的没填满的段(因为根据上面性质可知,左边界最大肯定有边界最小,我们先贪心地满足更紧的限制),此时若将这个段填满了就直接作为这个段右边界。 考虑我们如何实现这个填数过程,考虑区间 dp,设 $f_{l,r,S}$ 满足下标为 $[l,r]$ 的数已经填满集合为 $S$ 的段的最小贡献。 由上面的填数过程可知,我们不会在内部的小段留一些数给外面更大的段。(调整法也可以证明) 因此,设 $len_S$ 表示集合为 $S$ 的段的长度之和,满足 $f_{l,r,S}$ 最小且 $r-l+1$ 最小的 $l,r$ 一定满足 $r-l+1=len_S$。 于是我们只用考虑三种转移, 1. 目前左边/右边的数留给下一个包住它的段,$f_{l,r,S}=\min(f_{l+1,r,S},f_{l,r-1,S})$,这部分转移是 $O(1)$ 的,复杂度等于状态数 $O(2^Kn^2)$。 2. 把两端拼起来,考虑枚举子集,$f_{l,r,S}$ 可以从 $f_{l,l+len_{S'}-1,S'}+f_{r-len_{S-S'}+1,r,S-S'}$ 转移过来,复杂度为 $O(3^{K}n^2)$。 3. 用一个大段包住中间的小段,$f_{l,r,S}$ 可以从 $f_{l+1,r-1,S-\{x\}}+F_x(a_l,a_r)$,其中 $F_x(a_l,a_r)$,表示第 $x$ 段最小值为 $a_l$ 最大值为 $a_r$ 的贡献,这部分由于用大段包住小段的时候一定满足 $r-l+1=len_S$,所以复杂度为 $O(n2^{K}K)$ 的。 最后复杂度为 $O(3^Kn^2)$,瓶颈为第二种转移。 ## 代码 ```cpp #include<bits/stdc++.h> using namespace std; int n,k; int a[305]; int b[305]; int ned[305]; int f[305][305][128]; int sum[128]; int L[305],R[305]; bool pd[305]; vector<int>sb; int main(){ scanf("%d%d",&n,&k); int ans=0; for(int i=1;i<=n;i++)scanf("%d",&a[i]),ans+=2*a[i]; a[0]=1e6;a[n+1]=1e6;ans+=2e6; for(int i=2;i<=k+1;i++)scanf("%d",&b[i]),pd[b[i]]=true; b[1]=0;b[k+2]=n+1; for(int i=1;i<=n;i++) if(!pd[i])sb.push_back(a[i]); int K=0; for(int i=1;i<=k+1;i++){ if(b[i]==b[i+1]-1){ans+=abs(a[b[i+1]]-a[b[i]]);continue;} ned[++K]=b[i+1]-b[i]-1;L[K]=a[b[i]];R[K]=a[b[i+1]]; if(L[K]>R[K])swap(L[K],R[K]); }sb.push_back(-1); sort(sb.begin(),sb.end()); int m=sb.size()-1; for(int j=0;j<(1<<K);j++) for(int i=1;i<=K;i++) if(j&(1<<i-1))sum[j]+=ned[i]; for(int l=0;l<=m+1;l++) for(int r=0;r<=m+1;r++) for(int j=0;j<(1<<K);j++) f[l][r][j]=(j==0?0:1e9); for(int i=1;i<=m;i++){ for(int l=1;l+i-1<=m;l++){ int r=l+i-1; for(int j=0;j<(1<<K);j++){ f[l][r][j]=min(f[l+1][r][j],f[l][r-1][j]); if(sum[j]>i)continue; if(sum[j]==i){ for(int x=1;x<=K;x++) if(j&(1<<x-1)){ if(ned[x]==1&&i==1)f[l][r][j]=min(f[l][r][j],abs(L[x]-sb[l])+abs(R[x]-sb[l])); else if(ned[x]>1) f[l][r][j]=min(f[l][r][j],f[l+1][r-1][j^(1<<x-1)]+abs(L[x]-sb[l])+abs(R[x]-sb[r])+sb[r]-sb[l]); } } for(int s=(j-1)&j;s;s=(s-1)&j) f[l][r][j]=min(f[l][r][j],f[l][l+sum[s]-1][s]+f[l+sum[s]][r][j^s]); // cerr<<l<<" "<<r<<" "<< j<<" "<<f[l][r][j]<<endl; } } } ans+=f[1][m][(1<<K)-1];//cerr<<f[1][m][(1<<K)-1]<<endl; ans/=2;ans-=2e6; printf("%d\n",ans); return 0; } ```