bzoj2448 挖油

题意

已知一个长度为n的数列,其中前x个数是1,后n-x个数是0.(x是未知的).
可以付出ti的代价询问第i个数是1还是0.
现在需要求x的值,采用最优策略,最坏情况下的代价是多少.

分析

定义f[i][j]表示已知i-1<=x<=j时,采取最优策略,直到查询出x的值的最坏代价.枚举下一次询问的位置k(i<=k<=j),那么\(f[i][j]=min(t[k]+max(f[i][k-1],f[k+1][j]))\)
显然f[i][k]随着k的增加是单调递增的,f[k][j]随着k的增加是递减的.所以对于[i,j]存在某个分界点p,使得对于所有\(i\le k\le p\),有\(f[i][k]\le f[k][j]\),对所有\(p<k<=j\),有\(f[i][k]\gt f[k][j]\).
另一个结论是,对于所有长度为len的区间[i,i+len],随着i的递增,p的位置也是递增的.(可以认为现在有两条直线,一条斜率为正,一条斜率为负,现在把其中一条向上抬,另一条向下按,想象交点横坐标的移动方向).
现在按照所有区间长度进行DP,先DP较短区间.
这里我之前猜了不少结论,比如可以三分决策点,比如四边形不等式,比如决策单调性,没一个是对的,感受自己的愚蠢
在DP时我们可以维护p.考虑k在p的左侧和右侧两种情况,就可以把方程里的max(f[i][k-1],f[k+1][j])干掉.
现在我们分别需要查询t[k]+f[i][k-1]和t[k]+f[k+1][j]的最小值.这个东西可以写线段树,但据说会卡常数.既然我不会写zkw线段树,那就写树状数组好了23333.开2n个树状数组,分别对应n个左端点和右端点,例如L[i]这个树状数组里存储所有左端点为i的状态(f[i][i],f[i][i+1],f[i][i+2]...)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2005;
int g[maxn][maxn],f[maxn][maxn];
int n;
int t[maxn];
struct BIT1{
  int c[maxn];
  BIT1(){
    memset(c,0x7f,sizeof(c));
  }
  void add(int x,int w){
    for(;x;x-=x&(-x))c[x]=min(c[x],w);
  }
  int qmin(int x){
    int ans=0x7f7f7f7f;
    for(;x<maxn;x+=x&(-x))ans=min(ans,c[x]);
    return ans;
  }
}C1[maxn];
struct BIT2{
  int c[maxn];
  BIT2(){
    memset(c,0x7f,sizeof(c));
  }
  void add(int x,int w){
    for(;x<maxn;x+=x&(-x))c[x]=min(c[x],w);
  }
  int qmin(int x){
    int ans=0x7f7f7f7f;
    for(;x;x-=x&(-x))ans=min(ans,c[x]);
    return ans;
  }
}C2[maxn];
int main(){
  int n;scanf("%d",&n);
  for(int i=1;i<=n;++i)scanf("%d",t+i);
  for(int i=1;i<=n;++i){
    f[i][i]=t[i];
    if(i+1<=n)C1[i].add(i+1,f[i][i]+t[i+1]);
    if(i-1>=1)C2[i].add(i-1,f[i][i]+t[i-1]);
  }
  for(int len=1;len<n;++len){
    int pt=1;
    for(int i=1;i+len<=n;++i){
      int j=i+len;
      // f[i][j]=0x7f7f7f7f;
      // for(int k=i;k<=j;++k){
      //   f[i][j]=min(f[i][j],max(f[i][k-1],f[k+1][j]);
      // }
      while(f[i][pt-1]<f[pt+1][j])++pt;
      f[i][j]=min(C1[i].qmin(pt),C2[j].qmin(pt-1));
      if(j+1<=n)C1[i].add(j+1,f[i][j]+t[j+1]);
      if(i-1>=1)C2[j].add(i-1,f[i][j]+t[i-1]);
    }
  }
  printf("%d\n",f[1][n]);
  return 0;
}

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