Codeforces 704B. Ant Man 题解
题目大意:
- 给定\(n\)个元素,每一个元素有\(x_i,a_i,b_i,c_i,d_i\)。
- 求一个\(1\)~\(n\)的全排列\(p_1,p_2,\dots,p_n\)使得其价值最小,其中\(p_1=s,p_n=e\)。
- 定义一个排列的价值为\(\sum_{i=1}^{n-1}f(p_i,p_{i+1})\)。
- 函数\(f(i,j)\)定义为\(f(i,j)=\left \{\begin{array}{}x_i-x_j+c_i+b_j(i>j)\\x_j-x_i+d_i+a_j(i<j) \end{array}\right.\)。
- 求所有满足条件排列的最小价值。
题目链接:704B. Ant Man。
题解:在原题中,很容易考虑到将\(a_i\)变为\(a_i-x_i\),\(b_i,c_i,d_i\)以此类推,所以就可以消除\(x_i\),仅考虑\(a_i,b_i,c_i,d_i\)对答案的影响。
考虑 DP。设\(f_{i,j}\)表示从小到大选择了前\(i\)个数,一共分成了\(j\)个连续段的最小价值,然后对于每加入一个数,考虑它自己单独作为一段,连在一个段左端,连在一个段右端,连在两个段中间四种情况来考虑。对于起点和终点,只需要分两个段考虑即可。转移方程自己在草稿纸上画一下应该就能出来。
但是这一道题有一个比较麻烦的地方,就是起点不能往左合并,终点不能往右合并,这一种情况要考虑全,然后在转移中通过特判删掉这种转移方法即可。
时间复杂度\(o(n^2)\)。
据说还有更加快速的贪心算法,但是我没有考虑了。
下面是代码。
#include <cstdio>
#include <cstring>
template<typename Elem>
Elem min(Elem a,Elem b){
return a<b?a:b;
}
typedef long long ll;
const int Maxn=5000;
const ll Inf=0x3f3f3f3f3f3f3f3fll;
ll f[Maxn+5][Maxn+5];
int n,s,e;
int x[Maxn+5],a[Maxn+5],b[Maxn+5],c[Maxn+5],d[Maxn+5];
int main(){
scanf("%d%d%d",&n,&s,&e);
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&b[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&c[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&d[i]);
}
for(int i=1;i<=n;i++){
a[i]+=x[i];
c[i]+=x[i];
b[i]-=x[i];
d[i]-=x[i];
}
memset(f,0x3f,sizeof f);
f[0][0]=0;
for(int i=1;i<=n;i++){
if(i!=s&&i!=e){
for(int j=1;j<=i;j++){
f[i][j]=min(f[i][j],f[i-1][j-1]+b[i]+d[i]);
if(j>(i>e)||i==n){
f[i][j]=min(f[i][j],f[i-1][j]+a[i]+d[i]);
}
if(j>(i>s)||i==n){
f[i][j]=min(f[i][j],f[i-1][j]+b[i]+c[i]);
}
if(j+1>(i>e)+(i>s)||i==n){
f[i][j]=min(f[i][j],f[i-1][j+1]+a[i]+c[i]);
}
}
}
else if(i==s){
for(int j=1;j<=i;j++){
if(j>(i>e)||i==n){
f[i][j]=min(f[i][j],f[i-1][j-1]+d[i]);
f[i][j]=min(f[i][j],f[i-1][j]+c[i]);
}
}
}
else{
for(int j=1;j<=i;j++){
if(j>(i>s)||i==n){
f[i][j]=min(f[i][j],f[i-1][j-1]+b[i]);
f[i][j]=min(f[i][j],f[i-1][j]+a[i]);
}
}
}
}
printf("%lld\n",f[n][1]);
return 0;
}
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。