这里是 的做法。
我们先从一个朴素的 dp讲起。
设 表示我处理到 号节点的最小代价和,那么转移有:
那么我们可以进行一步步优化到 。
先关注 的情况,有一些我们不得不拆的 ,我把它们称作“代价”。
这些代价不仅和当前 的位置是有关,还与 的高度有关。
我们发现代价是不好计算的。随着 的变化,代价也随之变化,所以每次都要 扫描。
换一种思路,我们可以考虑每个 对答案的贡献,把它们挂在对应的节点上,就可以快速计算。
也就是说,我们要找到对于每一个 ,找到第一个 ,使得选中 时必须要拆除 。
具体而言,我们可以在 数组中 ,把 加到对应的高度上。
那么每次我想选一个 ,就必须要拆除所有挂在 上的代价,因为它们没有被选中且未被挡住。(其中 )
这样我们砍掉了计算区间贡献的 ,而换成了 。
再想想,由于 数组是单调的,所以我们可以一遍扫一遍存下来 的结果,就可以做到 了。
但此时,我们的转移点还是不确定,并且没有考虑 的情况。
对于 的部分,我们贪心的想肯定是越选多越好,所以除了把 选中的情况我们的答案都应该加上这些 。
没错,这里的影响就只和 的位置有关了,同样有关的还有我们的 数组。
其实我们 的本质,就是在最小化这些东西,而之前的操作是为了方便计算必须要拆的代价。
我们设 对应的 的位置为 , 之前 部分的和为 ,那么转移有:
其中 表示在 位置保留 高度的代价。
我们发现 里面的可以记一个前缀最小值,这样就可以 转移了。
这样最小化了能够最小化的部分,也统计了代价。
最后注意几点:
-
为了保证最后答案能够统计到,要在 的位置插一个极大值。
-
如果存在 对于任意的 ,那么它不能参与统计答案,但必须要参与代价以及 的计算。
-
其实 和 数组根本没必要开。~
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const int N=5e6+9;
const ll INF=1e18;
inline int read(){
int res=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
res=(res<<1)+(res<<3)+ch-'0';
ch=getchar();
}
return res*f;
}
int n,a[N],b[N],Q[N],m,pos[N];
ll f[N],g[N],cost[N],p[N],sum;
int main()
{
// freopen("hs.in","r",stdin);
// freopen("offa.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) p[i]=read();
n++,a[n]=n;
m=read();
for(int i=1;i<=m;i++) b[i]=read();
for(int i=1;i<=m;i++) pos[b[i]]=i;
m++,b[m]=n,pos[n]=m;
for(int i=1;i<N;i++) g[i]=INF;
for (int i=1,j=1;i<=n;++i)
{
if(b[j]<i) j++;
Q[i]=j;
}
ll tmp=0;
for (int i=1;i<=n;i++)
{
int j=pos[a[i]];
if(j)
{
if(g[j-1]>=INF) f[i]=INF;
else f[i]=g[j-1]+cost[j]+sum;
}
if(p[i]>=0) cost[Q[a[i]]]+=p[i];
else sum+=p[i];
if(j&&f[i]<INF) g[j]=min(g[j],f[i]-sum);
}
if (f[n]<INF) printf("%lld",f[n]);
else puts("Impossible");
return 0;
}