poj 動態規劃專題練習

http://poj.org/problem?id=2336ui

大意是要求一艘船將m個車運到對岸所消耗的最短期和最小次數spa

定義dp[i][j]運送前i個車,當前船上有j個車所消耗的時間,很是容易獲得以下ac代碼的推導blog

#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
get

這題能夠歸納爲兩步:string

1.求樹上點所能到達的最遠點,這類問題能夠說和求樹上點的直徑相似。樹上任意點的最遠點顯然就是直徑的兩個端點之一it

2.在一個序列中,求知足區間內最大值最小值之差不超過m的最長區間的大小io

問題1是經典的樹規,dp[u]爲向下走能走到的最遠值,dp1[u]爲不包括dp[u]的第一個結點所能走到的最大值,tp[u]爲向上走所能走到的最大值class

最遠點就是max(dp[u],tp[u]),分類討論可得tp[u]。轉移見代碼queue

問題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;
}
相關文章
相關標籤/搜索