这个专题让我学会了两种dp优化的方式,只要证明出决策单调的话,那么就可以进行斜率优化或者四边形优化。但前提是得能把dp的式子写出来。。然而我在做的时候连式子都写不出来。。
B - Lawrence
B题做的时候连式子都没推出来,现在看看真的是蠢。。
设dp[i][j] 为前i个炸j次的最小值,那么易得dp[i][j]=min(dp[k][j-1]+cost[k+1][i])。cost[i][j] 代表i到j能得到的价值,这个可以用区间和和区间平方和预处理出来,具体读者可以思考一下。
这样把式子化成斜率dp那样,就可以优化了。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
const ll inf=1e18;
ll dp[maxn][maxn];
int sum[maxn];
int sq[maxn];
int Q[maxn];
ll prod[maxn][maxn];
double K(int a,int b,int j)
{
double Y=2*dp[a][j-1]+sum[a]*sum[a]+sq[a]-2*dp[b][j-1]-sum[b]*sum[b]-sq[b];
double X=2*(sum[a]-sum[b]);
if(X==0)return inf;
return Y/X;
}
ll Cal(int idx,int i,int j)
{
return dp[idx][j-1]+prod[idx+1][i];
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m)&&n)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&sum[i]);
//sum[i]=100;
sq[i]=sum[i]*sum[i];
sum[i]+=sum[i-1];
sq[i]+=sq[i-1];
}
for(int k=0;k<=n;k++)
for(int i=k+1;i<=n;i++)
{
prod[k+1][i]=(1LL*(sum[i]-sum[k])*(sum[i]-sum[k])-(sq[i]-sq[k]))/2;
//cout<<prod[k+1][i]<<endl;
}
int first,tail;
for(int j=0;j<=m;j++)
{
first=tail=0;
for(int i=j;i<=n;i++)
{
if(j==0)
dp[i][j]=prod[1][i];
else
{
while(first<tail&&K(Q[first+1],Q[first],j)<=(double)sum[i])first++;
//cout<<first<<endl;
dp[i][j]=Cal(Q[first],i,j);
while(first<tail&&K(i,Q[tail],j)<=K(Q[tail],Q[tail-1],j))tail--;
Q[++tail]=i;
}
//cout<<i<<" "<<j<<" "<<first<<" "<<tail<<" "<<dp[i][j]<<endl;
}
}
cout<<dp[n][m]<<endl;
}
return 0;
}
C - 小明系列故事――捉迷藏
不知道为啥这个专题会有这个。。一眼看上去就是个bfs。。
预处理能看见大明和二明的位置,然后再bfs就完事了。
唯一需要注意的地方是,判断一个地方是否走过不能简单的用vis[x][y] 判断,因为没规定一个地方只能走一次,我们得再加个状态代表小明当前是否找到大明和二明的状态。
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
char Map[maxn][maxn];
int Dx,Dy,Ex,Ey,Sx,Sy;
int see_D_dx,see_D_ux,see_D_ly,see_D_ry;
int see_E_dx,see_E_ux,see_E_ly,see_E_ry;
int n,m,t;
void pre_init()
{
int tmpx=Dx,tmpy=Dy;
tmpy++;
while(tmpy<=m&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy++;
tmpy--;
see_D_ry=tmpy;
tmpy=Dy;
tmpy--;
while(tmpy>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy--;
tmpy++;
see_D_ly=tmpy;
tmpy=Dy;
tmpx++;
while(tmpx<=n&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx++;
tmpx--;
see_D_dx=tmpx;
tmpx=Dx;
tmpx--;
while(tmpx>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx--;
tmpx++;
see_D_ux=tmpx;
tmpx=Ex,tmpy=Ey;
tmpy++;
while(tmpy<=m&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy++;
tmpy--;
see_E_ry=tmpy;
tmpy=Ey;
tmpy--;
while(tmpy>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy--;
tmpy++;
see_E_ly=tmpy;
tmpy=Ey;
tmpx++;
while(tmpx<=n&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx++;
tmpx--;
see_E_dx=tmpx;
tmpx=Ex;
tmpx--;
while(tmpx>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx--;
tmpx++;
see_E_ux=tmpx;
//cout<<see_D_dx<<' '<<see_D_ux<<' '<<see_D_ly<<' '<<see_D_ry<<endl;
//cout<<see_E_dx<<' '<<see_E_ux<<' '<<see_E_ly<<' '<<see_E_ry<<endl;
}
int vis[maxn][maxn][5];
struct HeapNode
{
int x,y;
int d;
int state;
HeapNode(int _x,int _y,int _d,int _s):x(_x),y(_y),d(_d),state(_s){}
HeapNode(){}
bool operator<(const HeapNode &b)const
{
return d>b.d;
}
};
int Sta(int x,int y)
{
int res=0;
if(x==Dx&&y<=see_D_ry&&y>=see_D_ly)
res|=1;
else if(y==Dy&&x>=see_D_ux&&x<=see_D_dx)
res|=1;
if(x==Ex&&y<=see_E_ry&&y>=see_E_ly)
res|=2;
else if(y==Ey&&x>=see_E_ux&&x<=see_E_dx)
res|=2;
return res;
}
int dis[4][2]={-1,0,1,0,0,-1,0,1};
int solve()
{
//cout<<Sx<<' '<<Sy<<endl;
memset(vis,0,sizeof(vis));
queue<HeapNode>Q;
int itstate=Sta(Sx,Sy);
//cout<<itstate<<endl;
Q.push(HeapNode(Sx,Sy,0,itstate));
while(!Q.empty())
{
HeapNode u=Q.front();Q.pop();
if(vis[u.x][u.y][u.state])continue;
vis[u.x][u.y][u.state]=1;
if(u.d>t)return -1;
if(u.state==3)
{
//puts("yes");
return u.d;
}
for(int i=0;i<4;i++)
{
int tmpx=u.x+dis[i][0];
int tmpy=u.y+dis[i][1];
if(tmpx<1||tmpx>n||tmpy<1||tmpy>m)continue;
if(Map[tmpx][tmpy]=='.')
{
int tmp=(u.state|Sta(tmpx,tmpy));
//cout<<tmpx<<' '<<tmpy<<' '<<tmp<<endl;
Q.push(HeapNode(tmpx,tmpy,u.d+1,tmp));
}
}
}
return -1;
}
int main()
{
int T;
scanf("%d",&T);
int cas=1;
while(T--)
{
scanf("%d %d %d",&n,&m,&t);
for(int i=1;i<=n;i++)
scanf("%s",Map[i]+1);
printf("Case %d:\n",cas++);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(Map[i][j]=='D')
Dx=i,Dy=j;
else if(Map[i][j]=='E')
Ex=i,Ey=j;
else if(Map[i][j]=='S')
Sx=i,Sy=j;
}
pre_init();
printf("%d\n",solve());
}
return 0;
}
F - Cross the Wall
好题。
我们可以仔细思考思考,对于一个方块来说,如果存在另一个方块,长和宽都大于它,那么这个方块就没必要存在了。基于这个理论,我们可以先预处理出那些必须存在的方块,然后将其以宽增大,高减少的方式排列。再进行dp就完事了。
#include<bits/stdc++.h>
using namespace std;
const int maxn=50005;
const int maxh=1000005;
const int inf=0x3f3f3f3f;
int vis[maxh];
vector<int>V;
int stck[maxh],q[maxn];
struct Node
{
int w,h;
Node(int _w,int _h):w(_w),h(_h){}
Node(){}
bool operator<(const Node&b)const
{
return h>b.h;
}
}nodes[maxn];
long long dp[maxn][105];
double K(int a,int b,int i,int j)
{
double Y=dp[a][j-1]-dp[b][j-1];
double X=nodes[b+1].h-nodes[a+1].h;
return Y/X;
}
long long getup(int a,int b,int i,int j)
{
return dp[a][j-1]-dp[b][j-1];
}
long long getdown(int a,int b,int i,int j)
{
return nodes[b+1].h-nodes[a+1].h;
}
long long Cal(int a,int i,int j)
{
return dp[a][j-1]+1LL*nodes[a+1].h*nodes[i].w;
}
int main()
{
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
//cout<<(1LL<<60)<<endl;
int n,k;
while(~scanf("%d %d",&n,&k))
{
V.clear();
memset(vis,0,sizeof(vis));
int w,h;
for(int i=1;i<=n;i++)
{
scanf("%d %d",&w,&h);
vis[w]=max(vis[w],h);
}
for(int i=1;i<maxh;i++)if(vis[i])
V.push_back(i);
int top=0;
for(int i=0;i<V.size();i++)
{
int tmp=V[i];
while(top>0&&vis[V[stck[top]]]<=vis[tmp])top--;
stck[++top]=i;
}
int now=0;
for(int i=1;i<=top;i++)
nodes[++now]=Node(V[stck[i]],vis[V[stck[i]]]);
int first=0,tail=0;
long long ans=1e15;
for(int j=1;j<=k;j++)
{
first=tail=0;
for(int i=1;i<=now;i++)
{
if(j==1)
{
dp[i][j]=1LL*nodes[i].w*nodes[1].h;
continue;
}
while(first<tail&&getup(q[first+1],q[first],i,j)<=1LL*nodes[i].w*getdown(q[first+1],q[first],i,j))first++;
dp[i][j]=Cal(q[first],i,j);
while(first<tail&&getup(i,q[tail],i,j)*getdown(q[tail],q[tail-1],i,j)<=getup(q[tail],q[tail-1],i,j)*getdown(i,q[tail],i,j))tail--;
q[++tail]=i;
}
}
for(int i=1;i<=k;i++)
ans=min(ans,dp[now][i]);
cout<<ans<<endl;
}
return 0;
}
H - Tree Construction
这个题公式也没推出来,看了题解才发现好奇妙啊。
dp[i][j] 代表第i个点到第j个点建成树的最小花费。
那么dp[i][j]=min(dp[i][k]+dp[k+1][j]+cost(i,j,k));
cost(i,j,k)=nodes[k].x-nodes[i].x+nodes[k+1].y-nodes[j].y
如果不理解cost的话可以自己画画图。
然后我们用四边形优化就可以做出来了。
讲道理真爽。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int inf=0x3f3f3f3f;
typedef pair<int,int>pii;
pii nodes[maxn];
int dp[maxn][maxn];
int s[maxn][maxn];
int main()
{
int n;
while(~scanf("%d",&n))
{
int x,y;
for(int i=1; i<=n; i++)
{
scanf("%d %d",&x,&y);
nodes[i]=make_pair(x,y);
}
memset(dp,inf,sizeof(dp));
for(int i=1; i<=n; i++)
{
dp[i][i]=0;
s[i][i]=i;
}
for(int i=n; i>=1; i--)
for(int j=i+1; j<=n; j++)
{
int tmp=inf;
int idx;
for(int k=s[i][j-1]; k<=s[i+1][j]; k++)
{
if(k+1>j)continue;
if(tmp>dp[i][k]+dp[k+1][j]+nodes[k+1].first-nodes[i].first+nodes[k].second-nodes[j].second)
{
tmp=dp[i][k]+dp[k+1][j]+nodes[k+1].first-nodes[i].first+nodes[k].second-nodes[j].second;
idx=k;
}
}
dp[i][j]=tmp;
s[i][j]=idx;
}
cout<<dp[1][n]<<endl;
}
}
I - Post Office
这个dp式子挺好想的,设dp[i][j] 为前i个村子有j个邮局。那么dp[i][j]=min(dp[k][j-1]+cost(k+1,i)),关键这个cost算得我头皮发麻。。可以发现把邮局放在正中央最好,那么cost也可以进行递推。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=305;
const int inf=0x3f3f3f3f;
int a[maxn];
int dp[35][maxn];
int s[35][maxn];
int w[maxn][maxn];
int v,p;
void getw()
{
for(int i=1;i<=v;i++)
{
w[i][i]=0;
for(int j=i+1;j<=v;j++)
w[i][j]=w[i][j-1]+a[j]-a[(i+j)>>1];
}
}
int main()
{
scanf("%d %d",&v,&p);
for(int i=1;i<=v;i++)
scanf("%d",&a[i]);
getw();
memset(dp,inf,sizeof(dp));
for(int i=1;i<=v;i++)
{
s[1][i]=0;
dp[1][i]=w[1][i];
}
for(int i=2;i<=p;i++)
for(int j=v;j>=i;j--)
{
int tmp=inf;
int idx;
s[i][v+1]=v;
for(int k=s[i-1][j];k<=s[i][j+1];k++)
{
if(tmp>dp[i-1][k]+w[k+1][j])
{
tmp=dp[i-1][k]+w[k+1][j];
idx=k;
}
}
dp[i][j]=tmp;
s[i][j]=idx;
}
cout<<dp[p][v]<<endl;
return 0;
}
J - Batch Scheduling
这也是一道好题。让我学会了如何换种方式思考问题。
一开始做的时候,写了一个从前往后推的式子,dp[i][j] 代表前i个分了j块,因为你要计算时间所以必须要维护块数,然后再斜率优化后复杂度为n^2,结果还是T了。。
看了题解后才发现从后往前推更好。。这样似乎就不需要维护块数了。但式子不是特别好想。
设dp[i]为从i到n需要的最少的时间,那么dp[i]=min(dp[j]+(s+T[i]+T[i+1]+…+T[j-1])*(F[i]+F[i+1]+…+F[n]))
当时在F为啥要加到j之后想了一会,之后就觉得挺有道理的,因为j之后的时间并没有算i到j-1的时间,所以要在这里补上去。
读者如果不理解的话可以自己思考思考。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=10005;
int T[maxn],F[maxn];
int Q[maxn];
long long dp[maxn];
int n,s;
long long getup(int j,int k)
{
return dp[j]-dp[k];
}
long long getdown(int j,int k)
{
return T[k-1]-T[j-1];
}
long long Cal(int i,int j)
{
return dp[j]+1LL*F[i]*(T[j-1]-T[i-1]+s);
}
int main()
{
scanf("%d %d",&n,&s);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&T[i],&F[i]);
T[i]+=T[i-1];
}
for(int i=n;i>=1;i--)
F[i]+=F[i+1];
int first=0,tail=0;
Q[0]=n+1;
for(int i=n;i>=1;i--)
{
while(first<tail&&getup(Q[first+1],Q[first])<=F[i]*getdown(Q[first+1],Q[first]))first++;
dp[i]=Cal(i,Q[first]);
while(first<tail&&getup(i,Q[tail])*getdown(Q[tail],Q[tail-1])<=getup(Q[tail],Q[tail-1])*getdown(i,Q[tail]))tail--;
Q[++tail]=i;
}
cout<<dp[1]<<endl;
}
斜率优化和四边形优化大概知识算是掌握了。只要能把式子写出来应该就会做了,关键是连式子都写不出来。。
为了跑进度,这个专题两个难题都没写,等忙完了会补上的。。