题解-代价

Posted wendigo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解-代价相关的知识,希望对你有一定的参考价值。

题解-代价

前置知识:前缀和,暴搜找规律。

参考资料

暂无


题目描述

代价

有一个 (n+2) 项的序列 (a_1,a_2,...,a_{n+2}),给你 (a_2sim a_{n+1})(a_1=a_{n+2}=1),每删除一个数 (a_i) 的代价是 (a_i imes a_{i-1} imes a_{i+1}),求删除序列的最小代价。

数据范围:(1le nle 10^6)(1le a_ile 10^4)


考场上我没做出来,自闭了。


这题的暴力非常好写,可以写个双向链表,这样删数时维护 (a_{i+1})(a_{i-1}) 就非常方便了。代码如下:

#include <bits/stdc++.h>
using namespace std;

//&Start
#define lng long long
#define lit long double
#define kk(i,n) "
 "[i<n]
const int inf=0x3f3f3f3f;
const lng Inf=1e17;

//&Data
const int N=1e6+10;
int n,l[N],r[N];
lng a[N],b[N],sm[N],tmp,ans=Inf;

//&Main
void dfs(int x,lng sum){//我写了一个ida*,但其实没必要
    if(sum+sm[(n+1-x)]>=ans) return;
    if(x==n+1){ans=sum;return;}
    for(int i=r[0];i!=n+1;i=r[i]){
        l[r[i]]=l[i],r[l[i]]=r[i];
        dfs(x+1,sum+a[i]*a[l[i]]*a[r[i]]);
        l[r[i]]=i,r[l[i]]=i;
    }
}
int main(){
    scanf("%d",&n);
    a[0]=a[n+1]=1;
    r[0]=1,l[n+1]=n;
    for(int i=1;i<=n;i++){
        scanf("%lld",a+i),b[i]=a[i];
        l[i]=i-1,r[i]=i+1;
        tmp=min(tmp,a[i]);
    }
    sort(b+1,b+n+1);
    for(int i=1;i<=n;i++)
        sm[i]=sm[i-1]+b[i];
    dfs(1,0);
    printf("%lld
",ans);
    return 0;
}

时间复杂度是必爆的,但是这种结论题,可以用暴搜找规律(我考场上没想到啊啊啊)。多造几个序列,最好小一点,要不然暴搜太慢。然后看暴搜最佳选择方案,代码如下:

#include <bits/stdc++.h>
using namespace std;

//&Start
#define lng long long
#define lit long double
#define kk(i,n) "
 "[i<n]
const int inf=0x3f3f3f3f;
const lng Inf=1e17;

//&Data
const int N=1e6+10;
int n,l[N],r[N],xuan[N],as[N];
lng a[N],tmp,ans=Inf;

//&Main
void dfs(int x,lng sum){
    if(sum+(n+1-x)*tmp>=ans) return;
    if(x==n+1){
        ans=sum;
        memcpy(as,xuan,sizeof xuan);
        return;
    }
    for(int i=r[0];i!=n+1;i=r[i]){
        l[r[i]]=l[i],r[l[i]]=r[i];
        xuan[x]=i;
        dfs(x+1,sum+a[i]*a[l[i]]*a[r[i]]);
        l[r[i]]=i,r[l[i]]=i;
    }
}
int main(){
    scanf("%d",&n);
    if(n>10) return printf("%d
",n),0;
    a[0]=a[n+1]=1;
    r[0]=1,l[n+1]=n;
    for(int i=1;i<=n;i++){
        scanf("%lld",a+i);
        l[i]=i-1,r[i]=i+1;
        tmp=min(tmp,a[i]);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++) printf("%d%c",as[i],kk(i,n));
    printf("%lld
",ans);
    return 0;
}

然后你会发现,最优删法大部分都是从两边开始删的,唯独类似于

1 1 5 1 1 //n=3

这样的序列,不得不从中间把大的数去掉。

然后你能会脑洞大开,但是最终的正确方法是——把每个两边是 (1) 并且不包含 (1) 的子序列从两边删,然后最后删 (1)

大致证明:

( exttt{First}) ... (1) 必须最后取。

如果不最后取 (a_i=1),那么取 (1) 还会有 (a_{i-1} imes a_{i+1}) 的代价,并且取走两边的数的代价肯定会有个因子 (a_{i-1} imes a_{i+1})

而最后取 (a_i=1),代价为 (1)(因为最后只剩 (1) 了),并且取 (a_{i-1})(a_{i+1}) 时也能把因子 (1) 用上。

( exttt{Second}) ... 每个不包含 (1) 并且两边是 (1) 的序列从两边删最佳。

如果从两边删就相当于代价只有相邻两项的乘积(因为左边或右边一定是 (1)),然后如果不从两边删代价至少为三项相乘,最后总和总是要大一点的(真是很口胡的证明,得出结论还是主要靠找规律)。


然后要知道得到了一个不包含 (1) 并且两边是 (1) 的区间 ([L,R]) 要怎么 (Theta(R-L+1)) 求最优取法(数据范围已经说明了时间复杂度)。

([L,R]) 最后取的数为 (a_t(Lle tle R)),那么从 (a_L) 一直取到 (a_{t-1}) 的代价是

[sumlimits_{i=L}^{t-1}a_i imes a_{i+1}]

因为每次取的数左边肯定是 (1)

同理,从 (a_R) 向左取到 (a_{t+1}) 的代价是

[sumlimits_{i=t+1}^{R}a_i imes a_{i-1}]

然后取 (a_t) 的代价是 (1 imes a_t imes 1=a_t)

所以总代价

[W_t=sumlimits_{i=L}^{t-1}a_i imes a_{i+1}+sumlimits_{i=t+1}^{R}a_i imes a_{i-1}+a_t]

而这个式子的前两项可以用前缀后缀和维护

区间 ([L,R]) 最优取法的代价就是

[min{W_t}(tin[L,R])]

所以拿到区间 ([L,R]) 求最优取法就是 (Theta(R-L+1))

因为最后每取一个 (1) 的代价是 (1),所以最后答案是

[sumlimits_{[L,R]}min{W_t(tin[L,R])}+sumlimits_{i=2}^{n+1}[a_i=1]]

所以总时间复杂度 (Theta(n))

Code

#include <bits/stdc++.h>
using namespace std;

//&Start
#define lng long long
#define lit long double
#define kk(i,n) "
 "[i<n]
const int inf=0x3f3f3f3f;
const lng Inf=1e17;

//&Data
const int N=1e6+10;
int n;
lng a[N],sm[N],ms[N],ans;

//&Main
void solve(int l,int r){
    for(int i=l+1;i<=r;i++) sm[i]=sm[i-1]+a[i]*a[i-1]; //前缀和
    for(int i=r-1;i>=l;i--) ms[i]=ms[i+1]+a[i]*a[i+1]; //后缀和
    lng res=Inf;
    for(int i=l;i<=r;i++) res=min(res,a[i]+sm[i]+ms[i]);
    ans+=(res==Inf)?0:res; //如果序列长度为0,特判一下
}
int main(){
    scanf("%d",&n),a[0]=a[n+1]=1;//用0~n+1代替1~n+2
    for(int i=1;i<=n;i++) scanf("%lld",a+i);
    for(int lst=0,i=1;i<=n+1;i++)if(a[i]==1) solve(lst+1,i-1),lst=i,ans++;
    printf("%lld
",ans-1);//为了方便统计区间,把n+2的1也算进去了,减掉
    return 0;
}

祝大家学习愉快!

以上是关于题解-代价的主要内容,如果未能解决你的问题,请参考以下文章

题解[Usaco2008 Oct]灌水

HDU 3339 In Action(最短路+背包)题解

[poj]3280 Cheapest Palindrome 题解

bzoj1003题解

[题解]SDOI2014LIS

POJ3469:Dual Core CPU——题解