P9744 「KDOI-06-S」消除序列 贪心做法

容易证明,全操作中只会使用至多一次操作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);
 }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值