容易证明,全操作中只会使用至多一次操作a,如果同时使用两次操作a,则端点位于左边的操作a会被位于右边的覆盖,相当于多做了一次无用功
我们发现,如果只是将 v 1 , v 2 . . . v i v_1,v_2...v_i v1,v2...vi 都变成0,则只使用输入中给出的a操作可能是不优的,有可能同时使用操作a与操作b可以使答案更优
我们再定义一个操作 A i A_i Ai,表示将 v 1 , v 2 . . . v i v_1,v_2...v_i v1,v2...vi 都变成0且使用的代价最小
容易得出转移方程
A
i
=
m
i
n
(
a
i
,
A
i
+
b
i
)
A_i=min(a_i,A_i+b_i)
Ai=min(ai,Ai+bi)
因为 A i A_i Ai 与 a i a_i ai 具有相同的性质,所以 A i A_i Ai 也像 a i a_i ai 一样至多使用一次
A i A_i Ai 操作使用过后,i的左半区间全部然为0(包括i这个位置),右半区间不变为1,此时将序列变为目标序列的最小代价即为把左半区间需要染为1的点染色的代价加上把右半区间需要染为0的点染色的代价加上 A i A_i Ai,其中前两个信息显然可以使用前缀和维护
考虑贪心,我们发现把i设在每个需要染为1的位置或每个需要染为1的位置的左侧一格一定更优
为什么?
A i A_i Ai,表示将 v 1 , v 2 . . . v i v_1,v_2...v_i v1,v2...vi 都变成0且使用的代价最小,也就是把i左侧的位置都染为0的最优方案,任何染色方案都不会比这更优
所以对于每组询问,我们只需在每一个p的位置枚举每个i,取代价的最小值即可
时间复杂度:
O
(
n
+
m
)
O(n+m)
O(n+m)
具体看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Max=5e5+5,inf=0x7fffffffffffffff;
int n,m,q,a[Max],b[Max],c[Max];
int in[Max],tb[Max],tot[Max],tc[Max];
inline int read()
{
char c;
int a=0;
c=getchar();
while(c<'0' or '9'<c)c=getchar();
while('0'<=c and c<='9')
{
a=a*10+c-'0';
c=getchar();
}
return a;
}
signed main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)b[i]=read();
for(int i=1;i<=n;i++)c[i]=read();
for(int i=n;i>=1;i--)
tb[i]=tb[i+1]+b[i];
for(int i=1;i<=n;i++)
a[i]=min(a[i],a[i-1]+b[i]);
scanf("%d",&q);
int ans;
while(q--)
{
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
in[i]=read();
tc[i]=tc[i-1]+c[in[i]];
}
tot[m+1]=0;
for(int i=m;i>=1;i--)tot[i]=tot[i+1]+b[in[i]];
ans=a[n]+tc[m];
for(int i=1;i<=m;i++)
{
ans=min(ans,a[in[i]-1]+tc[i-1]+tb[in[i]+1]-tot[i+1]);
ans=min(ans,a[in[i]]+tc[i]+tb[in[i]+1]-tot[i+1]);
}
printf("%lld\n",ans);
}
}