这题两种不同做法,普通的\(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的过程中按照数值从小到大向排列中插入元素。插入的过程中,排列是长这样的:
也就是序列被分为一些段,转移时可以在任意一段的左边或右边连上当前数;或者用当前的数连接两个相邻的段,让当前数变成峰顶;也可以新建一个段,让当前数当谷底。令\(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();
}