Codeforces 704B. Ant Man 題解
阿新 • • 發佈:2020-07-27
題目大意:
- 給定\(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; }