Codeforces 704 B Antman 题解 (dp,贪心,结论)

题目链接

这题两种不同做法,普通的\(O(n^2)\)和奇怪的\(O(nlogn)\)。如果用\(O(nlogn)\)的话可以加强到1e6。


做法1

时间复杂度\(O(n^2)\)

先把最终的排列随便画一个出来观察一下:

图中越高的数值越大
发现一个"峰顶"的数对最终花费的贡献是\(2x_i+a_i+c_i\),一个"谷底"的数对花费的贡献是\(-2x_i+d_i+b_i\),一个处在上坡的数的贡献是\(a_i+d_i\),一个处在下坡的数的贡献是\(c_i+b_i\),最左边和最右边的s和e也是类似的。考虑dp,dp的过程中按照数值从小到大向排列中插入元素。插入的过程中,排列是长这样的:
image
也就是序列被分为一些段,转移时可以在任意一段的左边或右边连上当前数;或者用当前的数连接两个相邻的段,让当前数变成峰顶;也可以新建一个段,让当前数当谷底。令\(dp_{i,j,0/1}\)表示加入到了数i,当前序列内有j个段,以及序列是否已经被连接成了一整个段(在i=n时才可能成立),此时的最小代价。注意在i<s的时候序列中是没有上面图中包含s的那一段的。对于t同理。转移是\(O(1)\)的,所以总复杂度\(O(n^2)\)

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

void fileio()
{
  #ifdef LGS
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
  #endif
}
void termin()
{
  #ifdef LGS
  std::cout<<"\n\nPROGRAM TERMINATED";
  #endif
  exit(0);
}

using namespace std;

void chmin(LL &x,LL y){if(x>y) x=y;}

LL n,s,e,h[100010],a[100010],b[100010],c[100010],d[100010],dp[2][5010][2];

int main()
{
  fileio();
  //freopen("furry.in","r",stdin);
  //freopen("furry.out","w",stdout);

  cin>>n>>s>>e;
  repn(i,n) scanf("%lld",&h[i]);
  repn(i,n) scanf("%lld",&a[i]);
  repn(i,n) scanf("%lld",&b[i]);
  repn(i,n) scanf("%lld",&c[i]);
  repn(i,n) scanf("%lld",&d[i]);
  
  rep(i,2) rep(j,5005) rep(p,2) dp[i][j][p]=1e18;
  dp[1][0][0]=0;
  repn(ii,n)
  {
    int i=ii&1;
    rep(j,ii+1) rep(con,2) if(dp[i][j][con]<1e18)
    {
      LL val=dp[i][j][con];
      //cout<<ii<<' '<<j<<' '<<con<<' '<<val<<endl;

      if(ii!=s&&ii!=e)
      {
        chmin(dp[i^1][j+1][con],val+b[ii]+d[ii]-h[ii]*2);
        if(j||ii>s) chmin(dp[i^1][j][con],val+a[ii]+d[ii]);
        if(j||ii>e) chmin(dp[i^1][j][con],val+b[ii]+c[ii]);
        if(j>0&&j+(int)(ii>s)+(int)(ii>e)>1) chmin(dp[i^1][j-1][con],val+a[ii]+c[ii]+h[ii]*2);
        if(ii==n&&j==0) chmin(dp[i^1][0][1],val+a[ii]+c[ii]+h[ii]*2);
      }
      else if(ii==s)
      {
        chmin(dp[i^1][j][con],val+d[ii]-h[ii]);
        if(j>0) chmin(dp[i^1][j-1][con],val+c[ii]+h[ii]);
        if(ii==n&&j==0) chmin(dp[i^1][j][1],val+c[ii]+h[ii]);
      }
      else
      {
        chmin(dp[i^1][j][con],val+b[ii]-h[ii]);
        if(j>0) chmin(dp[i^1][j-1][con],val+a[ii]+h[ii]);
        if(ii==n&&j==0) chmin(dp[i^1][j][1],val+a[ii]+h[ii]);
      }

      dp[i][j][con]=1e18;
    }
  }
  cout<<dp[(n+1)&1][0][1]<<endl;

  termin();
}

做法2

时间复杂度\(O(nlogn)\)

这题其实是可以贪心的。
先进行一些定义:令"大到i"的代价等于\(b_i-x_i\),"i到大"的代价等于\(d_i-x_i\),"小到i"的代价等于\(a_i+x_i\),"i到小"的代价等于\(c_i+x_i\)。观察题目中的代价计算方式,也很容易知道为什么这么定义。

结论
从小到大插入所有不是s且不是e的数,每次插入到增加的代价最小的位置就可以得到最优解。

证明
考虑三个数xyz,令\(x>y>z\),现在y在z前面,我们要把x插入到y和z之间。原来的代价是 y到小 + 大到z,现在的代价是 y到大 + 小到x + x到大 + 大到z。其中小到x + x到大是插入x的时候一定有的代价,区别就在于剩下的 "y到大-y到小";如果x>z>y也是类似的。如果s=1且e=2,或s=2且e=1,那我们每次插入都一定是把一个大数插入到两个小数之间。发现其实每插入一个数都会把一个"某到小"的代价变成"某到大",或是把"小到某"变成"大到某"。并且每个数只有两次作为这里的"某"的机会,左边一次右边一次,被消耗了就没了。所以每次我们插入的时候不如去找一个代价最小的机会插入,反正这个机会以后也不会消失。
现在考虑s、e不是1和2的情况。此时可能会出现把x插入到s和y之间,满足\(s>x>y\)的这种情况。可以把这种情况看做是先消耗了 小到x + x到小 的代价,然后又消耗了 大到x-小到x 的代价。这相当于是x消耗了自己提供的一次机会。跟上面的其他数提供的机会没有差别,我们仍然是选择代价最小的最优。

知道这个结论之后,用multiset维护在所有区间插入数的代价,每次取最小的即可。时间复杂度\(O(nlogn)\)

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <LL,LL>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

void fileio()
{
  #ifdef LGS
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
  #endif
}
void termin()
{
  #ifdef LGS
  std::cout<<"\n\nPROGRAM TERMINATED";
  #endif
  exit(0);
}

using namespace std;

LL n,s,e,h[100010],a[100010],b[100010],c[100010],d[100010],lnk[100010],head,tail,
tob[100010],bto[100010],tos[100010],sto[100010];
set <pair <LL,pii> > vals;

LL getCost(LL x,LL y)
{
  if(x<y) return tob[x]+sto[y];
  else return tos[x]+bto[y];
}

void add(LL x,LL y,LL op)
{
  LL val;
  if(x>y) val=tob[x]-tos[x];
  else val=bto[y]-sto[y];
  if(op==1) vals.insert(mpr(val,mpr(x,y)));
  else vals.erase(mpr(val,mpr(x,y)));
}

int main()
{
  fileio();
  //freopen("furry.in","r",stdin);
  //freopen("furry.out","w",stdout);

  cin>>n>>s>>e;
  repn(i,n) scanf("%lld",&h[i]);
  repn(i,n) scanf("%lld",&a[i]);
  repn(i,n) scanf("%lld",&b[i]);
  repn(i,n) scanf("%lld",&c[i]);
  repn(i,n) scanf("%lld",&d[i]);

  repn(i,n)
  {
    tob[i]=d[i]-h[i];
    sto[i]=a[i]+h[i];
    tos[i]=c[i]+h[i];
    bto[i]=b[i]-h[i];
  }
  int st;
  repn(i,n) if(i!=s&&i!=e)
  {
    st=i;
    break;
  }
  if(n==2)
  {
    cout<<getCost(s,e)<<endl;
    termin();
  }
  LL ans=getCost(s,st)+getCost(st,e);
  head=tail=st;
  for(int i=st+1;i<=n;++i) if(i!=s&&i!=e)
  {
    pair <LL,pii> opt=mpr(1e18,mpr(-1,-1));
    if(s>i) opt=min(opt,mpr(bto[i]+tos[i],mpr(s,head)));
    else if(head!=s)
    {
      add(s,head,1);
      lnk[s]=head;head=s;
    }
    if(e>i) opt=min(opt,mpr(sto[i]+tob[i],mpr(tail,e)));
    else if(tail!=e)
    {
      add(tail,e,1);
      lnk[tail]=e;tail=e;
    }
    if(!vals.empty())
    {
      pair <LL,pii> nn=*vals.begin();nn.fi+=sto[i]+tos[i];
      opt=min(opt,nn);
    }
    ans+=opt.fi;
    if(opt.se.fi==s&&head!=s)
    {
      vals.insert(mpr(tob[i]-tos[i],mpr(i,head)));
      lnk[i]=head;head=i;
    }
    else if(opt.se.se==e&&tail!=e)
    {
      vals.insert(mpr(bto[i]-sto[i],mpr(tail,i)));
      lnk[tail]=i;tail=i;
    }
    else
    {
      add(opt.se.fi,opt.se.se,-1);
      add(opt.se.fi,i,1);add(i,opt.se.se,1);
      lnk[opt.se.fi]=i;lnk[i]=opt.se.se;
    }
  }
  cout<<ans<<endl;

  termin();
}
posted @ 2022-11-19 21:28  LegendStane  阅读(44)  评论(0)    收藏  举报