您的位置:首页 > 其它

poj 动态规划专题练习

2017-05-30 11:26 288 查看
http://poj.org/problem?id=2336

大意是要求一艘船将m个车运到对岸所消耗的最短时间和最小次数

定义dp[i][j]运送前i个车,当前船上有j个车所消耗的时间,非常容易得到如下ac代码的推导

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1508;
const int INF=0x7f7f7f7f;
int dp[maxn][maxn];
int w[maxn];
int main()
{
int i,j,cas,n,m,t;
scanf("%d",&cas);
while(cas--)
{
scanf("%d%d%d",&n,&t,&m);
for(i=1;i<=m;i++)
scanf("%d",&w[i]);
for(j=1;j<=n;j++)
dp[1][j]=w[1]+t;
int sum=w[1]+t,ans=1;
for(i=2;i<=m;i++)
{
//	printf("fuck %d\n",sum);
dp[i][1]=max(sum+t,w[i])+t;
for(j=2;j<=n;j++)
{
if(w[i]>dp[i-1][j-1]-t)
dp[i][j]=dp[i-1][j-1]+w[i]-w[i-1];
else
dp[i][j]=dp[i-1][j-1];
}
sum=INF;
for(j=1;j<=n;j++)
if(sum>dp[i][j])
sum=dp[i][j];
}
/*	for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
printf("%dfuck%d ",num[i][j],dp[i][j]);
printf("\n");
}*/
ans=m/n;
if(m%n)	ans++;
printf("%d %d\n",sum,ans);
}
return 0;
}


  

  但似乎看起来还是有些不对?这里的状态转移没有类似于dp[i][j]=max(dp[i][j],。。)这样取最优状态的做法,我们这样定义状态可能是多余的。事实上只要dp[i]就可以记录状态了。除了dp以外还有贪心做法:我们运送所有车辆的最短时间,就是要最后一辆车的等待时间最小:第一次运m%n个,然后每次运n个,就可以得到最优解了

poj 3162 Walking Race


这题可以概括为两步:

1.求树上点所能到达的最远点,这类问题可以说和求树上点的直径类似。树上任意点的最远点显然就是直径的两个端点之一

2.在一个序列中,求满足区间内最大值最小值之差不超过m的最长区间的大小

问题1是经典的树规,dp[u]为向下走能走到的最远值,dp1[u]为不包括dp[u]的第一个结点所能走到的最大值,tp[u]为向上走所能走到的最大值

最远点就是max(dp[u],tp[u]),分类讨论可得tp[u]。转移见代码

问题2也是经典的规划问题,用单调队列可以维护区间最大值和最小值

我们单调队列的做法是:开两条单调队列,一条维护最大值一条维护最小值。插入元素时,若插入新元素到队尾破坏了队列的单调性,则抛弃原有的队尾元素直到新元素的插入不破坏队列的单调性。每次拿出两个队首的元素进行判断,若max-min>m,删除下标靠前的队首元素,重新判断条件是否成立。若不成立继续删直到成立为止。 若max-min>m,考虑更新答案。

队列的插入删除手段实际上是根据所求的一种贪心--当更大更靠右的元素在队首,之前入队的元素可以抛弃(嘛,)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1000008;
struct fuck{
int u,v,w,next;
}edge[maxn<<1];
int tol;
int head[maxn];
void init()
{
tol=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)
{
edge[tol].u=u;
edge[tol].v=v;
edge[tol].w=w;
edge[tol].next=head[u]++;
head[u]=tol++;
}
int dp[maxn],dpx[maxn],tp[maxn],dp1[maxn];
void dfs(int u,int pre)
{
int i;
dp[u]=0;
for(i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(edge[i].v==pre)	continue;
dfs(v,u);
if(dp[v]+edge[i].w>dp[u])
{
dp[u]=dp[v]+edge[i].w;
dpx[u]=v;
}
}
}
void dfs1(int u,int pre)
{
int i;
dp1[u]=0;
for(i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==pre)	continue;
dfs1(v,u);
if(v!=dpx[u]&&dp[v]+edge[i].w>dp1[u])
dp1[u]=dp[v]+edge[i].w;
}
}
void dfs2(int u,int pre)
{
int i;
for(i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==pre)	continue;
if(dpx[u]==v)
tp[v]=max(dp1[u],tp[u])+edge[i].w;
else
tp[v]=max(dp[u],tp[u])+edge[i].w;
dfs2(v,u);
}
}
int ans[maxn];
int mx[maxn];
int mi[maxn];
int solve(int n,int m)
{
int mxf=0,mxb=0;
int mif=0,mib=0;
int res=0;
int left=1;
for(int i=1;i<=n;i++)
{
while(mxf>mxb&&ans[mx[mxf-1]]<=ans[i])
mxf--;
mx[mxf++]=i;
while(mif>mib&&ans[mi[mif-1]]>=ans[i])
mif--;
mi[mif++]=i;
if(ans[mx[mxb]]-ans[mi[mib]]<=m)
{
int sum=i-left+1;
if(sum>res)	res=sum;
//	printf("%d %d\n",left,i);
}
else
{
//	printf("%d\n",i);
while(mib<mif&&mxb<mxf&&ans[mx[mxb]]-ans[mi[mib]]>m)
{
if(mx[mxb]>mi[mib])
{
left=mi[mib]+1;
mib++;
}
else
{
left=mx[mxb]+1;
mxb++;
}
}
}
}
return res;
}
int main()
{
int n,m,u,v;
while(scanf("%d%d",&n,&m)==2)
{
init();
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,i+1,v);
addedge(i+1,u,v);
}
dfs(1,-1);
dfs1(1,-1);
tp[1]=0;
dfs2(1,-1);
for(int i=1;i<=n;i++)
ans[i]=max(dp[i],tp[i]);
//	for(int i=1;i<=n;i++)	printf("%d ",ans[i]);
int res=solve(n,m);
printf("%d\n",res);
}
return 0;
}


  

poj1947 Rebuilding Roads

经典树规,求在一颗树中获得一颗节点数为p的子树最少需要砍掉多少条边

这题有两个没有说明的地方,其一是,数据中给出的子树总是以1为根的。其二是,所求的子树可以不包含根节点

定义dp[u][i][j]为以u为根节点的树处理了前i个子节点并获得一颗j节点的子树最少需要砍掉的边,初始dp[u][0][1]为u节点的度数,然后有以下转移:



if(dp[u][idx-1][j-x]!=INF&&dp[v][du[v]][x]!=INF)

{
dp[u][idx][j]=min(dp[u][idx-1][j-x]+dp[v][du[v]][x]-1,dp[u][idx][j]);
}



之后根据枚举根节点可以这样求得答案:


int ans=dp[1][du[1]][p];
for(int i=2;i<=n;i++)
if(dp[i][du[i]][p]+1<ans)
ans=dp[i][du[i]][p]+1;


  

dp的过程也不难理解,就是相当于将每颗子树看成一个分组,将每颗子树可能取的值当作分组中的物品然后跑一遍分组背包。理解分组背包的情况下不难想到


#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=158;
const int INF=0x7f7f7f7f;
struct fuck{
int u,v,next;
}edge[maxn<<1];
int tol;
int head[maxn];
void init()
{
tol=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
edge[tol].u=u;
edge[tol].v=v;
edge[tol].next=head[u];
head[u]=tol++;
}
int dp[maxn][maxn][maxn];
int du[maxn];
int num[maxn];
void dfs(int u,int p)
{
int i;
dp[u][0][1]=du[u];
num[u]=1;
int idx=0;
for(i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
dfs(v,p);
num[u]+=num[v];
idx++;
for(int x=1;x<=p;x++)
dp[u][idx][x]=dp[u][idx-1][x];
for(int j=1;j<=p;j++)
for(int x=1;x<j;x++)
{
if(dp[u][idx-1][j-x]!=INF&&dp[v][du[v]][x]!=INF)
{
dp[u][idx][j]=min(dp[u][idx-1][j-x]+dp[v][du[v]][x]-1,
dp[u][idx][j]);
}
}
}
}
int main()
{
int n,p,u,v;
while(scanf("%d%d",&n,&p)==2)
{
memset(dp,INF,sizeof(dp));
init();
memset(du,0,sizeof(du));
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
du[u]++;
}
dfs(1,p);
int ans=dp[1][du[1]][p]; for(int i=2;i<=n;i++) if(dp[i][du[i]][p]+1<ans) ans=dp[i][du[i]][p]+1;printf("%d\n",ans);
}
return 0;
}


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: