hdu 3507 Print Article
题意:
给长度为n的数组a,要求切割成若干个连续区间:
切割完之后每个块的成本是:(区间和)2+M
问切割的最小成本是多少
思路:
学自bin巨:
d[i]表示前i个的最小费用,转移方程为d[i]=min{d[j]+(sum[i]-sum[j])^2+M},(j<i)
复杂度是O(n^2)的,但是n<=2e5,复杂度不满足要求,还需要优化
假设在计算d[i]的时候,k<j且k不如j优,则:
d[j]+(sum[i]-sum[j])^2+M<d[k]+(sum[i]-sum[k])^2+M,移项+化简可得:
[(d[j]+sum[j]^2)-(d[k]+sum[k]^2)]/(2sum[j]-2sum[k])<sum[i]
令y[j]=d[j]+sum[j]^2,x[j]=2sum[j],则式子变为:
(y[j]-y[k])/(x[j]-x[k])<sum[i],发现这是斜率式,且右边的sum[i]是递增的
令g[j,k]=(y[j]-y[k])/(x[j]-x[k])
1.去掉劣解:如果满足j比k优,即g[j,k]<sum[i],因为右边sum[i]是递增的,所以对于i后面的i+1等
都满足这个式子,也就是说j一直都比k优,那么k可以被淘汰
2.准备插入i:假设i>j>k,g[i,j]<g[j,k],那么j可以被淘汰
如果g[i,j]<sum[i],即i比j优,j可以被淘汰,
如果g[i,j]>sum[i],因为g[i,j]<g[j,k],所以有g[j,k]>sum[i],那么k比j优,j可以被淘汰
(注意不等式符号方向)
用队列维护,具体操作方法见代码
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=5e5+5;
int q[maxm],head,tail;
int sum[maxm];
int d[maxm];
int n,m;
int dp(int i,int j){
return d[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+m;
}
int up(int j,int k){//y[j]-y[k]
return (d[j]+sum[j]*sum[j])-(d[k]+sum[k]*sum[k]);
}
int down(int j,int k){//x[j]-x[k]
return 2*(sum[j]-sum[k]);
}
signed main(){
while(scanf("%d%d",&n,&m)!=EOF){
for(int i=1;i<=n;i++){
scanf("%d",&sum[i]);
sum[i]+=sum[i-1];
}
head=tail=0;//head=tail的时候表示队列为空
q[tail++]=0;//d[i]可能从d[0]转移,因此插入一个0
for(int i=1;i<=n;i++){
//不等式的相除转化为相乘,不过不转可能也不会wa
//head+1<tail说明队内至少有两个元素
while(head+1<tail){//去掉劣解;
int j=q[head+1],k=q[head];
int x=up(j,k);
int y=sum[i]*down(j,k);
if(x<=y)head++;//这时j比k优,因此去掉k
else break;
}
d[i]=dp(i,q[head]);//现在队头就是最优的j
while(head+1<tail){//准备插入i;
int j=q[tail-1],k=q[tail-2];
int x=up(i,j)*down(j,k);
int y=up(j,k)*down(i,j);
if(x<=y)tail--;//g[i,j]<=g[j,k]
else break;
}
q[tail++]=i;
}
printf("%d\n",d[n]);
}
return 0;
}
bzoj1096 [ZJOI2007]仓库建设
题意:
思路:
d[i]为在i位置建立仓库的最小代价
d[i]=min{d[j]+cal(j,i)}+c[i]},其中cal(j,i)是把(j+1)到i的所有物品存到i的花费
cal(j,i)=x[i]*sigma(p[k])-sigma(x[k]*p[k]),其中k的取值为j+1<=k<=i
sigma(p[k])和 sigma(x[k]*p[k])可以用前缀和预处理
设s[]=p[]的前缀和,ss[]为x[]*p[]的前缀和
那么cal(j,i)=x[i]*(s[i]-s[j])-(ss[i]-ss[j])
那么总式子就是 d[i]=min{d[j]+x[i]*(s[i]-s[j])-(ss[i]-ss[j])}
现在假设j优于k,那么:
d[j]+x[i]*(s[i]-s[j])-(ss[i]-ss[j])<d[k]+x[i]*(s[i]-s[k])-(ss[i]-ss[k])
化简移项一下变为:
[(d[j]+ss[j])-(d[k]+ss[k])]/(s[j]-s[k])<x[i]
令y[j]=d[j]+ss[j],x[j]=s[j]
则式子变为(y[j]-y[k])/(x[j]-x[k])< x[i],是一个斜率式
斜率优化一下就行了
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e6+5;
int x[maxm],p[maxm],c[maxm];
int q[maxm],head,tail;
int s[maxm],ss[maxm];
int d[maxm];
int n;
int dp(int i,int j){
return d[j]+x[i]*(s[i]-s[j])-(ss[i]-ss[j])+c[i];
}
int up(int j,int k){
return (d[j]+ss[j])-(d[k]+ss[k]);
}
int down(int j,int k){
return s[j]-s[k];
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>x[i]>>p[i]>>c[i];
}
for(int i=1;i<=n;i++){
s[i]=s[i-1]+p[i];
ss[i]=ss[i-1]+x[i]*p[i];
}
head=tail=0;
q[tail++]=0;
for(int i=1;i<=n;i++){
while(head+1<tail){
int j=q[head+1],k=q[head];
int l=up(j,k);
int r=x[i]*down(j,k);
if(l<=r)head++;
else break;
}
d[i]=dp(i,q[head]);
while(head+1<tail){
int j=q[tail-1],k=q[tail-2];
int l=up(i,j)*down(j,k);
int r=up(j,k)*down(i,j);
if(l<=r)tail--;
else break;
}
q[tail++]=i;
}
cout<<d[n]<<endl;
return 0;
}
bzoj1010 [HNOI2008]玩具装箱toy
题意:
n<=5e4,L,c(i)<=1e7
思路:
d[i]=min{d[j]+cal(j,i)}
cal(j,i)=[i-(j+1)+sigma(c[k])-L]^2 ,其中j+1<=k<=i
设s[]为c[]的前缀和,再改变一下项的位置,则式子变为:
d[i]=min{d[j]+[(s[i]+i)-(s[j]+j+1+L)]^2};
令a[i]=s[i]+i,b[i]=s[i]+i+1+L,缩项之后式子为:
d[i]=min{d[j]+(a[i]-b[j])^2}
假设j比k优,则有式子:
d[j]+(a[i]-b[j])^2<d[k]+(a[i]-b[k])^2,移项化简一下得:
[(d[j]+b[j]^2)-(d[k]+b[k]^2)]/(2*b[j]-2*b[k])<a[i]
预处理s[],a[],b[]即可
总结:推式子的时候可以缩项,更容易接下来的推导
ps:
这题要注意边界的初始化,b[0]=s[0]+0+1+L,即b[0]=L+1
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=5e4+5;
int s[maxm],a[maxm],b[maxm];
int q[maxm],head,tail;
int d[maxm];
int n,L;
int dp(int i,int j){
return d[j]+(a[i]-b[j])*(a[i]-b[j]);
}
int up(int j,int k){
return (d[j]+b[j]*b[j])-(d[k]+b[k]*b[k]);
}
int down(int j,int k){
return 2*(b[j]-b[k]);
}
signed main(){
cin>>n>>L;
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]+=s[i-1];
a[i]=s[i]+i;
b[i]=s[i]+i+1+L;
}
b[0]=L+1;
head=tail=0;
q[tail++]=0;
for(int i=1;i<=n;i++){
while(head+1<tail){
int j=q[head+1],k=q[head];
int x=up(j,k);
int y=a[i]*down(j,k);
if(x<=y)head++;
else break;
}
d[i]=dp(i,q[head]);
while(head+1<tail){
int j=q[tail-1],k=q[tail-2];
int x=up(i,j)*down(j,k);
int y=up(j,k)*down(i,j);
if(x<=y)tail--;
else break;
}
q[tail++]=i;
}
cout<<d[n]<<endl;
return 0;
}
其他:
网上看到的技巧:
dp[i]=a[i]+b[j],O(n^2),用单调队列可以优化到O(n)
dp[i]=a[i]*b[j]+c[i]+d[j],因为有a[i]*b[j],因此单调队列不行了,用斜率优化