bzoj4709[JSOI2011]柠檬

题意:Flute 很喜欢柠檬。它准备了一串用树枝串起来的贝壳,打算用一种魔法把贝壳变成柠檬。贝壳一共有 N (1 ≤ N≤ 100,000) 只,按顺序串在树枝上。为了方便,我们从左到右给贝壳编号 1..N。每只贝壳的大小不一定相同,贝壳 i 的大小为 si(1 ≤ si ≤10,000)。变柠檬的魔法要求,Flute 每次从树枝一端取下一小段连续的贝壳,并选择一种贝壳的大小 s0。如果 这一小段贝壳中大小为 s0 的贝壳有 t 只,那么魔法可以把这一小段贝壳变成 s0t^2 只柠檬。Flute 可以取任意多次贝壳,直到树枝上的贝壳被全部取完。各个小段中,Flute 选择的贝壳大小 s0 可以不同。而最终 Flute 得到的柠檬数,就是所有小段柠檬数的总和。Flute 想知道,它最多能用这一串贝壳变出多少柠檬。请你帮忙解决这个问题。

分析:首先,按照题意,从这串贝壳的左边和右边都可以取下贝壳,然后如果按照这个题意设计状态,同时考虑左边和右边拿到了哪个地方,这题就不可做了….有一个很简单的性质:得分只和最后分割的结果有关,和选取的顺序无关,然后我们定义f[i]为对前i个贝壳进行分割的最大收益就好了.(这个性质我没看出来…捂脸熊).

接下来,如果一个区间左端点的si和这个区间选取的s0不同,那么我们把这个左端点拿出来单独成为一个区间肯定可以得到更优的解.也就是说,最优解中每个区间的左右端点的si都等于这个区间选取的s0,记sum[i]为从1到i有多少个贝壳大小和si相同,那么我们发现问题变成了1D1D动态规划,转移的形式中有平方,容易想到斜率优化,推一推发现不能O(n)斜率优化但可以O(nlogn).我们对每个贝壳大小分别用vector维护一个队列,总的空间复杂度还是O(n)

注意vector中如果当前有3个元素,维护一个尾指针,把尾指针减1之后,push_back(x)会把x添加到第3个元素之后而不是第2个元素之后,需要pop_back()一下.

#include<cstdio>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=100005,maxw=10005;
vector<int> Q[maxw];
int head[maxw],tail[maxw];
int lastappear[maxw];
int a[maxn],s[maxn];
ll f[maxn];
ll y(int j,int c0){
  return f[j]+c0*1LL*(s[j+1]-1)*1LL*(s[j+1]-1);
}
double k(int j1,int j2,int c0){
  return (y(j1,c0)-y(j2,c0))/double(s[j1+1]-s[j2+1]);
}
int main(){
  int n;scanf("%d",&n);
  for(int i=1;i<=n;++i){
    scanf("%d",&a[i]);
  }
  for(int i=1;i<=n;++i){
    s[i]=s[lastappear[a[i]]]+1;lastappear[a[i]]=i;
  }
  for(int i=1;i<=n;++i){
    vector<int> &q=Q[a[i]];
    while(head[a[i]]+1<tail[a[i]]&&k(q[tail[a[i]]-1],q[tail[a[i]]-2],a[i])<k(i-1,q[tail[a[i]]-1],a[i]))tail[a[i]]--,q.pop_back();
    q.push_back(i-1);tail[a[i]]++;
    int j;
    //if(i==8)printf("%lld %lld %lld\n",y(0,4),y(3,4),y(7,4));
    //if(i==8)printf("%d %d %d\n",tail[4],q[0],q[1]);
    if(tail[a[i]]==1){
      j=0;
    }
    else if(k(q[tail[a[i]]-1],q[tail[a[i]]-2],a[i])>2*a[i]*s[i]){
      j=tail[a[i]]-1;//printf("!");
    }else if(k(q[head[a[i]]],q[head[a[i]]+1],a[i])<2*a[i]*s[i]){
      j=head[a[i]];
    }else{
      int l=1,r=tail[a[i]]-2;
      while(l<=r){
    int mid=(l+r)>>1;
    if(k(q[mid-1],q[mid],a[i])>2*a[i]*s[i])l=mid+1;
    else r=mid-1;
      }
      j=l-1;
    }//printf("%d\n",q[j]);
    j=q[j];
    f[i]=f[j]+a[i]*1LL*(s[i]-s[j+1]+1)*(s[i]-s[j+1]+1);
  }
  printf("%lld\n",f[n]);
  return 0;
}

 

posted @ 2017-02-16 08:01  liu_runda  阅读(1025)  评论(0编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难