这周讲的东西主要是一些 DP 优化。
1800 文明与法治
题意:你知道未来
- 第
天至多购买 股,至多卖出 股; - 某一天的买入或卖出均算作一次交易在。在两次交易之间至少要间隔
天; - 任何时候手中的股票数不能超过
。
在第
解析:原题为[SCOI2010]股票交易。
一道经典的单调队列 DP。假设
那么考虑如何转移。如果第
否则,可分成买入或卖出两种交易。若在第
即从
即从
现在考虑初始状态如何设置。由于第一天前有无限的钱,因此这
同时将其他状态都赋成极小值。
如果暴力转移以上方程,复杂度为
代码:
//
// Created by 屋顶上的小丑 on 2023/3/13.
//
#include<cstdio>
#include<cstring>
const int maxn=2e3;
int T,MaxP,W,q[maxn+5];
int ap[maxn+5],bp[maxn+5];
int as[maxn+5],bs[maxn+5];
long long f[maxn+5][maxn+5];
int max(int a,int b)
{
return (a>b)?a:b;
}
int main()
{
scanf("%d%d%d",&T,&MaxP,&W);
for(int i=1;i<=T;i++)
scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
memset(f,-0x3f,sizeof(f));
for(int i=1;i<=T;i++)
for(int j=0;j<=as[i];j++)
f[i][j]=-j*ap[i];
for(int i=1;i<=T;i++)
{
for(int j=0;j<=MaxP;j++)
f[i][j]=max(f[i][j],f[i-1][j]);
if(i<=W)
continue;
int las=i-W-1;
int l=1,r=0;
for(int j=0;j<=MaxP;j++)
{
while(l<=r&&q[l]<j-as[i]) l++;
if(l<=r)
f[i][j]=max(f[i][j],f[las][q[l]]+(q[l]-j)*ap[i]);
while(l<=r&&f[las][q[r]]+q[r]*ap[i]<=f[las][j]+j*ap[i]) r--;
q[++r]=j;
}
l=1,r=0;
for(int j=MaxP;j>=0;j--)
{
while(l<=r&&q[l]>j+bs[i]) l++;
if(l<=r)
f[i][j]=max(f[i][j],f[las][q[l]]+(q[l]-j)*bp[i]);
while(l<=r&&f[las][q[r]]+q[r]*bp[i]<=f[las][j]+j*bp[i]) r--;
q[++r]=j;
}
}
printf("%lld",f[T][0]);
return 0;
}
1511 wennitao卖冰墩墩
题意:一条路上有
解析:非常经典的斜率优化。
设
设
这里已经可以看出斜率优化的雏形了,我们继续转化,令
设
代码:
//
// Created by 屋顶上的小丑 on 2023/3/13.
//
#include<cstdio>
#include<cstring>
const int maxn=1e6;
int n,x[maxn+5],p[maxn+5];
int q[maxn+5],c[maxn+5];
long long sum1[maxn+5],sum2[maxn+5];
long long f[maxn+5];
long long a(int i)
{
return c[i]+x[i]*sum1[i]-sum2[i];
}
long long b(int i)
{
return f[i]+sum2[i];
}
double k(int i)
{
return (double)x[i];
}
long long X(int i)
{
return sum1[i];
}
long long Y(int i)
{
return b(i);
}
double slope(int i,int j)
{
return (double)(Y(i)-Y(j))/(X(i)-X(j));
}
long long min(long long x,long long y)
{
return (x<y)?x:y;
}
int main()
{
scanf("%d",&n);
f[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&x[i],&p[i],&c[i]);
sum1[i]=sum1[i-1]+p[i];
sum2[i]=sum2[i-1]+1ll*p[i]*x[i];
}
int l=1,r=1;
for(int i=1;i<=n;i++)
{
while(l<r&&slope(q[l],q[l+1])<k(i)) l++;
f[i]=b(q[l])+a(i)-1ll*sum1[q[l]]*x[i];
while(l<r&&slope(q[r-1],q[r])>slope(q[r],i)) r--;
q[++r]=i;
}
printf("%lld",f[n]);
return 0;
}
1802 破败的灯塔国(加强版)
题意:一条直线上有
解析:原题为[JSOI2016]灯塔。
设第
我们只考虑
然后就能发现决策单调性了。由于
代码:
//
// Created by 屋顶上的小丑 on 2023/3/13.
//
#include<cstdio>
#include<cmath>
const int maxn=1e6;
int n,h[maxn+5];
double ans[maxn+5];
inline double max(double a,double b)
{
return a>b?a:b;
}
inline double calc(int i,int j)
{
return h[j]+sqrt(abs(i-j));
}
template<class T>
void swap(T &x,T &y)
{
T temp=x;
x=y;
y=temp;
}
void solve(int l,int r,int L,int R)
{
if(l>r)
return ;
int mid=(l+r)>>1,pos=0;
double las=0;
for(int i=L;i<=mid&&i<=R;i++)
{
double now=calc(mid,i);
if(las<now)
{
las=now;
pos=i;
}
}
ans[mid]=max(ans[mid],las);
solve(l,mid-1,L,pos);
solve(mid+1,r,pos,R);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]);
solve(1,n,1,n);
for(int i=1;i<=n/2;i++)
{
swap(h[i],h[n-i+1]);
swap(ans[i],ans[n-i+1]);
}
solve(1,n,1,n);
for(int i=n;i>=1;i--)
printf("%d\n",(int)ceil(ans[i]-h[i]));
return 0;
}
1812 zhongzero的黑白树
题意:给定一个
解析:原题为[国家集训队]Tree I。
挺简单的题,用到了类似 wqs 二分(因为感觉并不算严格的 wqs 二分)的思路。
要求最小权生成树,显然要用最小生成树算法。我们思考一下 kruskal 算法求解最小生成树的原理,它需要先按边权从小到大进行排序再依次对边进行选择,边权越小的边会优先被选择。因此我们可以通过改变白边的权值来改变它们被选中的机会。我们考虑二分一个额外值
//
// Created by 屋顶上的小丑 on 2023/3/13.
// 第四道题,用了algorithm库中的sort
//
#include<cstdio>
#include<cmath>
#include<algorithm>
#define INF 1e9
using namespace std;
const int maxn=1e5;
struct edge
{
int x;
int y;
int z;
int col;
}s[maxn+5];
bool cmp(edge a,edge b)
{
if(a.z==b.z)
return a.col<b.col;
return a.z<b.z;
}
int n,m,need,maxx,k;
int fa[maxn+5],ans,sum;
int getfa(int x)
{
if(fa[x]==x)
return x;
return fa[x]=getfa(fa[x]);
}
bool check(int x)
{
for(int i=1;i<=m;i++)
if(!s[i].col)
s[i].z+=x;
for(int i=1;i<=n+1;i++)
fa[i]=i;
sum=k=0;
sort(s+1,s+m+1,cmp);
int tot=0;
for(int i=1;i<=m;i++)
{
if(tot==n-1)
break;
int a=getfa(s[i].x);
int b=getfa(s[i].y);
if(a==b)
continue;
else
{
tot++;
fa[a]=b;
sum+=s[i].z;
k+=(s[i].col^1);
}
}
return k>=need;
}
void clean(int x)
{
for(int i=1;i<=m;i++)
if(!s[i].col)
s[i].z-=x;
}
int main()
{
scanf("%d%d%d",&n,&m,&need);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&s[i].x,&s[i].y,&s[i].z,&s[i].col);
s[i].x++;
s[i].y++;
maxx=max(maxx,s[i].z);
}
int l=-INF,r=INF,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid))
{
ans=sum-need*mid;
l=mid+1;
}
else
r=mid-1;
clean(mid);
}
printf("%d\n",ans);
return 0;
}

