首页 > 其他分享 >Codeforces 704 B Antman 题解 (dp,贪心,结论)

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

时间:2022-11-19 21:33:21浏览次数:70  
标签:opt val 704 题解 Codeforces ii mpr se 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();
}

标签:opt,val,704,题解,Codeforces,ii,mpr,se,dp
From: https://www.cnblogs.com/legendstane/p/codeforces-704-b-antman-solution-dp-greedy.html

相关文章

  • C. Sum of Substrings题解
    C.SumofSubstrings题目大概意思,给你一个01串,求和最小,其中和是该串所有相邻字符所组成的十进制数的和。如:0110,sum=01+11+10=22。通过观察我们可以发现,除了第......
  • cf796部分题解
    C.ManipulatingHistory题意:给出一些字符串,有原始串(只含一个字符的串)、被替换的串、替换串、最终串(最后一行),求原始串。2aabbcdacdInitiallysis"a".Inthe......
  • B - Bracket Sequence题解
    B-BracketSequence思路:用一个flag来标记括号的数目,如果括号数目是个偶数的话,就代表当前要执行'+'操作,反之就是'*'操作。对于最外层的数,是没有计算的。所以最后要单独......
  • Auxiliary Set题解
    FAuxiliarySet树上LCA+DFS注意一下输出格式!#include<bits/stdc++.h>usingnamespacestd;constintN=1e5+10;intt,n,q,ans;intfa[N];//存储点i的......
  • 广东工业大学第十六届程序设计竞赛题解(部分)
    E爬塔方法一:二分做法预处理每个点所能到达的最远距离,存到vector里边,然后二分处理结果#include<bits/stdc++.h>usingnamespacestd;constintN=1e5+10;intn,......
  • F - Subarrays题解
    F-Subarrays题意:给你一个序列,问这个序列里有多少个子串的和能被k整除。思路:求前缀和,然后每个位置对k取模,模数相等的位置之间,是一个满足条件的字串。因为求的是前缀和,......
  • G water testing题解
    Gwatertesting题意:给你一个多边形(可能是凸多边形,也可能是凹多边形),问该多边形内有多少个整数点(不包含边界)。思路:皮克定理+叉乘计算三角形面积:皮克定理是指一个计算点......
  • Frogger题解
    Frogger法一:floyd#include<iostream>#include<cstring>#include<algorithm>#include<cstdio>#include<cmath>#include<iomanip>#defineintlonglongintusingn......
  • 2022 zafu校赛题解
    A煎饼哥哥好鲨题读入时,分别统计四种不同提交结果,最后按题目要求输出即可。代码链接B富哥磊神暴力枚举三种纸币的数量,统计合法付款方式的数量即可。注意优化暴力枚举......
  • python(牛客)试题解析2 - 中等
    导航一、NC192二叉树的后序遍历二、NC117 合并二叉树三、求长度最长的的连续子序列使他们的和等于sum四、按顺序取出固定长度内容并合并两个数组为一个新数组五、输......