📅  最后修改于: 2023-12-03 15:42:14.461000             🧑  作者: Mango
本题为 门 | Gate IT 2008。
有 $n$ 台电脑,它们从左往右依次编号为 $1,2,\cdots,n$,开关门的操作均在左侧完成,第 $i$ 台电脑开关门所需的时间为 $t_i$ 秒。
现在门已经全部关上,一些电脑已开机,需要按照某种排列顺序将这些电脑关机再打开。具体地,在该序列中,每个开机的电脑必须在另一个电脑之前关机再打开。请注意,每个打开时刻需要先关机,等到所有电脑关机完毕以后再打开。
对于每一次操作,给出电脑编号 $x$ 和操作类型 $p$。形式化地,当 $p=0$ 时为开机,$p=1$ 时为关机。打开或关闭门的时间不计算在内,并且不考虑时间的浪费。
请在优先考虑电脑 $i$,然后考虑电脑 $i+1$,以此类推的顺序下,计算完成所有操作的最短时间。
第一行包含整数 $n$,表示电脑的数量。
接下来的 $n$ 行依次表示每个电脑的关机再打开所需的时间。
接下来两行表示开机和关机序列。每行包含一个整数 $m$,表示序列中电脑的数量。接下来 $m$ 个正整数,表示序列中出现的电脑编号,按照给出的顺序。
一个整数,表示完成所有操作的最短时间。
$1 \leqslant n \leqslant 10^3$,$1 \leqslant t_i \leqslant 3600$,$1 \leqslant m \leqslant n$
5
2
1
3
1
2
2
1 5
13
(动态规划) $O(n^2)$
状态表示: $f_{i,j}$ 表示电脑 $a_i$ 在电脑 $a_{i+1}$ 前关机再打开,经过了开关 $s_1,s_2,\cdots,s_j$ 的最短时间。
最终答案: $\min\limits_{j=1}^n f_{1,j}+t_{a_1}$。
状态计算:对于状态 $f_{i,j}$,根据上一个开机的电脑编号 $b$,判断电脑 $a_i$ 是否已经开机,然后有两种决策:
$f_{i,j}= \begin{cases} f_{i,j-1} & \text{if $a_i$ 未启动或 $\forall k<i,a_k,a_k+1 \notin {s_1,s_2,\cdots,s_j}$}\ \min(f_{i+1,j}+t_{a_{i+1}}+t_{a_i},f_{i,j-1}) & \text{otherwise} \end{cases}$
时间复杂度: $O(n^2)$。
C++ 代码
#include<bits/stdc++.h>
#include<cstring>
using namespace std;
const int MAXN = 1010;
int n,m,t,ans=0x7fffffff;
int t1[MAXN],a[MAXN],b[MAXN];
int f[MAXN][MAXN];
void init(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&t1[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
}
void DP(){
for(int j=1;j<=n;j++){
for(int i=j-1;i>=1;i--){
f[i][j]=0x7fffffff;
if(a[i]){
if(a[i-1] && j!=n){
f[i][j]=min(f[i][j],f[i+1][j]+t1[a[i+1]]+t1[a[i]]);
}
if(j!=n){
f[i][j]=min(f[i][j],f[i+1][j]+t1[a[i+1]]*2);
}
if(b[j+1]){
f[i][j]=min(f[i][j],f[i][j-1]+t1[a[i]]+t1[b[j+1]]);
}
f[i][j]=min(f[i][j],f[i][j-1]);
}
}
}
printf("%d",f[1][n]+t1[a[1]]);
}
int main(){
init();
DP();
return 0;
}
Python 代码
def doit():
for j in range(one,two+1):
for i in range(j-1,0,-1):
f[i][j]=0x7fffffff
if a[i]!=0:
if i-1>=1 and a[i-1]!=0 and j!=n:
f[i][j]=min(f[i][j],f[i+1][j]+t1[a[i+1]]+t1[a[i]])
if j!=n and a[i+1]!=0:
f[i][j]=min(f[i][j],f[i+1][j]+t1[a[i+1]]*2)
if j+1<=n and b[j+1]!=0:
f[i][j]=min(f[i][j],f[i][j-1]+t1[a[i]]+t1[b[j+1]])
f[i][j]=min(f[i][j],f[i][j-1])
print(f[1][n]+t1[a[1]])
n=int(input())
arr=[int(x) for x in input().split()]
f=[[0]*110 for _ in range(110)]
for i in range(1,n+1):
t1[i]=arr[i-1]
one=int(input())
one_arr=[int(x) for x in input().split()]
for i in range(one):
a[i+1]=one_arr[i]
two=int(input())
two_arr=[int(x) for x in input().split()]
for i in range(two):
b[i+1]=two_arr[i]
doit()
(贪心) $O(n^2)$
对于任意一对相邻的电脑,设它们在序列中出现的下标分别为 $i$ 和 $j$,显然在它们之间插入一台电脑只会使答案变得更差。因为插入一台电脑会带来三个时间开销:先关机,等待下一台电脑关机,再打开。因此我们贪心地选取开机序列中每个电脑之前位置中,尽量靠右的电脑进行关机。显然这要求 $i$ 和 $j$ 之前的序列中所有电脑都已经全部开机。所以可以通过一个指针滚动处理。具体地,维护两个指针 $k$ 和 $i$ 表示当前已经开机且在等待关机的电脑和下一个关机电脑的位置,然后倒序扫描关机序列,如果发现一个电脑和 $a_i$ 不同,就将 $k$ 往左移动一个位置,并将这台电脑关机(如果它还未关机)。扫描完之后再将 $a_i$ 关机。如此操作一遍之后,更新答案,并将 $i$ 往前移动一个位置。当 $i=n+1$ 时,算法结束。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1010;
int n,m,t,ans=0x7fffffff;
int t1[MAXN],a[MAXN],b[MAXN];
int f[MAXN];
void init(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&t1[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
}
void solve(){
int p=1; f[n+1]=0;
for(int i=n;i>=1;i--){
if(a[i]){
int tmp=0x7fffffff;
for(int j=p;j<i;j++){
if(a[j]!=a[i]){
if(!f[a[j]]) tmp=min(tmp,t1[a[j]]);
else if(f[a[j]]<f[a[i]]) tmp=min(tmp,t1[a[j]]*2);
}
}
f[a[i]]=f[a[i+1]]+t1[a[i+1]]+tmp;
ans=min(ans,f[a[i]]+t1[a[1]]);
}
else{
while(p<=n && f[a[p+1]]) p++;
f[a[p]]=f[a[i+1]]+t1[a[i+1]];
ans=min(ans,f[a[p]]+t1[a[1]]);
p++;
}
}
printf("%d",ans);
}
int main(){
init();
solve();
return 0;
}
Python 代码
def doit():
f[n+1]=0
p=1
for i in range(n,0,-1):
if a[i]!=0:
tmp=0x7ffffff
for j in range(p,i):
if a[j]!=a[i]:
if f[a[j]]==0:
tmp=min(tmp,t1[a[j]])
else:
if f[a[j]]<f[a[i]]:
tmp=min(tmp,t1[a[j]]*2)
f[a[i]]=f[a[i+1]]+tmp+t1[a[i+1]]
ans=min(ans,f[a[i]]+t1[a[1]])
else:
while p<=n and f[a[p+1]]!=0:
p+=1
f[a[p]]=f[a[i+1]]+t1[a[i+1]]
ans=min(ans,f[a[p]]+t1[a[1]])
p+=1
print(ans)
n=int(input())
arr=[int(x) for x in input().split()]
f=[0]*1100
for i in range(1,n+1):
t1[i]=arr[i-1]
m=int(input())
arr=[int(x) for x in input().split()]
for i in range(1,m+1):
a[i]=arr[i-1]
m=int(input())
arr=[int(x) for x in input().split()]
for i in range(1,m+1):
b[i]=arr[i-1]
doit()
C++ 代码参考自 zxybazhongsheng。
Python 代码参考自 LCX。