poj 动态规划专题练习

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;
}

  

时间: 2024-12-09 08:41:37

poj 动态规划专题练习的相关文章

{POJ}{动态规划}

动态规划与贪心相关: {POJ}{2479}{Maximum Sum} (DP) 摘要: 题意:给定n个数,求两段连续子列的最大和.思路:先从左向右dp,求出一段连续子列的最大和,再从右向左dp,求出两段连续子列的最大和,方法还是挺经典的. {POJ}{1036}{Gansters} (DP) 摘要: 题意:有个伸缩门,门的宽度0~K,每个时间可以伸长或缩短1个单位,有N个流氓,他们在T时刻到达,如果这时门的宽度正好与他们的stoutness相等时,便可获得一定的收入,问最大的收入是多少. 思路

poj 动态规划题目列表及总结

此文转载别人,希望自己能够做完这些题目! 1.POJ动态规划题目列表 容易:1018, 1050, 1083, 1088, 1125, 1143, 1157, 1163, 1178, 1179, 1189, 1208, 1276,1322, 1414, 1456, 1458, 1609, 1644, 1664, 1690, 1699, 1740(博弈),1742, 1887, 1926(马尔科夫矩阵,求平衡), 1936, 1952, 1953, 1958, 1959, 1962, 1975,

动态规划专题(一) HDU1087 最长公共子序列

Super Jumping! Jumping! Jumping! 首先对于动态规划问题要找出其子问题,如果找的子问题是前n个序列的最长上升子序列,但这样的子问题不好,因为它不具备无后效性,因为它的第n+1的数会影响前n个序列的长度,换句话说,如果第n+1个数加上去不一定使得和前n个数加起来就是最长子序列,具体例子很多比如5,6,1,2 第5个数是3,那么最长序列5,6加3不会比1,2加3长. 比较好的子问题是“求以第K个为终点的最长上升子序列”,其实这个子问题的好处在于它限定了一点,终点为第k个

动态规划专题 01背包问题详解【转】

对于动态规划,每个刚接触的人都需要一段时间来理解,特别是第一次接触的时候总是想不通为什么这种方法可行,这篇文章就是为了帮助大家理解动态规划,并通过讲解基本的01背包问题来引导读者如何去思考动态规划.本文力求通俗易懂,无异性,不让读者感到迷惑,引导读者去思考,所以如果你在阅读中发现有不通顺的地方,让你产生错误理解的地方,让你难得读懂的地方,请跟贴指出,谢谢! 初识动态规划 经典的01背包问题是这样的: 有一个包和n个物品,包的容量为m,每个物品都有各自的体积和价值,问当从这n个物品中选择多个物品放

动态规划专题总结

动态规划所适用的问题是一个问题可以被分成若干个阶段,每一个阶段都需要做出决策,并且影响的下一个阶段的决策. 动态规划常常适用于重叠子问题,和最优子结构,的问题. 动态规划背后的基本思想非常简单.大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解. 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表. 这种做法在重复子问题的数目关于输

动态规划专题小结:四边形不等式优化

今天第一次学习四边形不等式优化dp,感觉优化效果十分给力,不过数学味道比较浓重,证明比较复杂.因此这里删繁就简,给出关于四边形不等式优化必须要明白的地方,以后直接套用条件即可. 四边形不等式优化条件 在动态规划中,经常遇到形如下式的转台转移方程: m(i,j)=min{m(i,k-1),m(k,j)}+w(i,j)(i≤k≤j)(min也可以改为max) 上述的m(i,j)表示区间[i,j]上的某个最优值.w(i,j)表示在转移时需要额外付出的代价.该方程的时间复杂度为O(N^3). 下面我们通

动态规划专题小结:最长上升子序列(LIS)问题

(1)问题描述:给定n个整数A1,A2,A3...An.按照从左往右的顺序选择尽可能多的整数,组成一个上升子序列,其中相邻元素不能相等. (2)解题思路:本题就是经典的最长上升子序列问题(Longest Increasing Subsequence,LIS).可以通过动态规划解决.定义状态d(i)表示以下标i结尾的LIS的最大长度.那么不难得到如下状态转移方程: d(i)=max{0,d(j)|j<i,Aj<Ai}+1; 最终的答案为max{d(i)}.时间复杂度为O(N^2).由于这种方法十

动态规划专题 01背包问题详解 HDU 2546 饭卡

我以此题为例,详细分析01背包问题,希望该题能够为初学者对01背包问题的理解有所帮助,有什么问题可以向我提供,一同进步^_^ 饭卡 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 14246    Accepted Submission(s): 4952 Problem Description 电子科大本部食堂的饭卡有一种很诡异的设计,即

动态规划专题 多阶段决策问题 蓝桥杯 K好数

问题描述 如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数.求L位K进制数中K好数的数目.例如K = 4,L = 2的时候,所有K好数为11.13.20.22.30.31.33 共7个.由于这个数目很大,请你输出它对1000000007取模后的值. 输入格式 输入包含两个正整数,K和L. 输出格式 输出一个整数,表示答案对1000000007取模后的值. 样例输入 4 2 样例输出 7 数据规模与约定 对于30%的数据,KL <= 106: 对于50%的