📜  门| Gate IT 2008 |问题29(1)

📅  最后修改于: 2023-12-03 15:42:14.461000             🧑  作者: Mango

题目29:网关 IT 2008

本题为 门 | 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
算法1

(动态规划) $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$ 是否已经开机,然后有两种决策:

  1. 不对电脑 $a_i$ 进行操作,直接考虑下一个开机的电脑;
  2. 关闭电脑 $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()
算法2

(贪心) $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。