BZOJ2149拆迁队
【题目】
原题地址
题目大意不想写。
【题目分析】
斜率优化的dp是显然的,然后就是怎么维护的问题了。
【解题思路】
这题显然就是一个斜率优化的dp,然后分治的时候维护一下凸壳,但是我写了很久
首先考虑暴力的dp怎么做
表示保留旧房子
时,
最多能保留多少个旧房子
表示
为最优决策时,
的最小总花费
然后我们有dp式
的优化显然,接下来考虑对
的优化,去掉后面的
相关常数可以得到。
然后这就是一个斜率优化的形式,我们把这个东西写得简单一点。
设
最终我们可以得到:
要满足:
去掉关于 的常数后就是每次插入一个点 转移的时候拿斜率 的直线卡最小截距。
这个东西怎么实现呢?分治!
处理左半边对右半边的影响就是按照
排序,然后插入斜率。
因为插入顺序单调,所以我们只需要维护一个下凸壳,查询时在凸壳上三分。
时间复杂度
【参考代码】
#include<bits/stdc++.h>
#define mkp(x,y) make_pair(x,y)
using namespace std;
typedef long long LL;
typedef pair<int,LL> pii;
const LL INF=(LL)1e18;
const int N=1e5+10;
int n,mx;
int a[N],b[N],d[N],f[N],id[N];
LL ans,c[N],g[N];
vector<pii>vc;
bool cmp(int x,int y)
{
if(d[x]==d[y])
return x<y;
return d[x]<d[y];
}
pii operator -(pii A,pii B){
return mkp(A.first-B.first,A.second-B.second);
}
LL operator *(pii A,pii B){
return A.first*B.second-A.second*B.first;
}
void solve(int l,int r)
{
if(l==r)
return;
int mid=(l+r)>>1,cnt=0;
solve(l,mid);
for(int i=l;i<=r;++i)
id[++cnt]=i;
sort(id+1,id+cnt+1,cmp);
pii now;
int mx=-1,L,R,m1,m2;
for(int i=1;i<=cnt;++i)
if(id[i]<=mid)
{
if(f[id[i]]<mx)
continue;
if(id[i]!=0 && !f[id[i]])
continue;
if(f[id[i]]>mx)
vc.clear(),mx=f[id[i]];
now=mkp(2*d[id[i]],c[id[i]]+2*g[id[i]]);
while(vc.size()>1 && (vc[vc.size()-1]-vc[vc.size()-2])*(now-vc[vc.size()-1])<=0)
vc.pop_back();
vc.push_back(now);
}
else
{
if(f[id[i]]>mx+1)
continue;
if(f[id[i]]<mx+1)
g[id[i]]=INF;
f[id[i]]=mx+1;
L=0,R=vc.size()-1;
while(L+2<R)
{
m1=(R-L)/3+L;m2=(R-L)/3*2+L;
if(1ll*vc[m1].first*id[i]+vc[m1].second>1ll*vc[m2].first*id[i]+vc[m2].second)
L=m1+1;
else
R=m2-1;
}
for(int j=L;j<=R;++j)
g[id[i]]=min(g[id[i]],(1ll*vc[j].first*id[i]+1ll*id[i]*(id[i]-1)+vc[j].second)/2+a[id[i]]+b[id[i]]);
}
solve(mid+1,r);
}
int main()
{
freopen("BZOJ2149.in","r",stdin);
freopen("BZOJ2149.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
scanf("%d",&b[i]);
for(int i=1;i<=n;++i)
{
g[i]=INF;d[i]=a[i]-i;
c[i]=1ll*i*(i+1)-2ll*i*a[i]-2ll*a[i];
}
solve(0,n);
for(int i=1;i<=n;++i)
{
g[i]+=1ll*(n-i)*(a[i]+1)+1ll*(n-i)*(n-i-1)/2;
if(mx<f[i])
mx=f[i],ans=g[i];
else
if(mx==f[i])
ans=min(ans,g[i]);
}
printf("%d %lld\n",mx,ans);
return 0;
}
CF660F Bear and Bowling 4
【题目】
原题地址
题目大意:给一个长度为
的序列,第
个数是
,选取一个连续子序列
使得
最大。
【题目分析】
同上。
【解题思路】
记
为
的前缀和。
为每一个数值乘以所在位置的值的前缀和。
那么
。所求的即
的最大值。
然后这个显然是一个斜率优化的形式。接下来对于一个点 ,我们往前找 要满足 这个值最大。就是维护 的下凸壳。凸壳上二分就行了。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N=2e5+10;
LL n,res;
LL a[N],pre[N],pre2[N],c[N];
vector<LL>q;
double slope(LL x,LL y)
{
return (double)(c[x]-c[y])/(x-y);
}
LL binary(LL x,LL l,LL r)
{
while(l+1<r)
{
LL mid=(l+r)>>1;
if(slope(q[mid],q[mid-1])>x)
l=mid;
else
r=mid;
}
return q[l];
}
int main()
{
freopen("CF660F.in","r",stdin);
freopen("CF660F.out","w",stdout);
scanf("%I64d",&n);
for(LL i=1;i<=n;++i)
{
scanf("%I64d",&a[i]);
pre[i]=pre[i-1]+a[i];
pre2[i]=pre2[i-1]+a[i]*i;
c[i]=1ll*i*pre[i]-pre2[i];
}
q.push_back(0);
for(LL i=1;i<=n;++i)
{
LL j=binary(pre[i],0,q.size());
res=max(res,pre2[i]-pre2[j]-j*(pre[i]-pre[j]));
while(q.size()>1 && slope(q[q.size()-1],q[q.size()-2])<slope(i,q[q.size()-1]))
q.pop_back();
q.push_back(i);
}
printf("%I64d\n",res);
return 0;
}
【总结】
为了讲分治写的,没想到斜率优化在
不连续的时候还可以这么玩。