kuangbin专题十二 基础DP

A - HDU1024 Max Sum Plus Plus

一维code:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+10,inf=0x3f3f3f3f;
int pre[N],f[N],a[N],m,n;

int main()
{
	while(~scanf("%d %d",&m,&n))
	{
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		
		memset(pre,0,sizeof(pre));
		memset(f,0,sizeof(f));
		int Max;
		for(int i=1;i<=m;i++)
		{
			Max=-inf;
			for(int j=i;j<=n;j++)
			{
				f[j]=max(f[j-1],pre[j-1])+a[j];
				pre[j-1]=Max;
				Max=max(Max,f[j]);
			}
		}
		printf("%d\n",Max);
	}
	return 0;
}

二维code(慎用):

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+10,inf=0x3f3f3f3f;
int f[100][N],a[N],m,n;

int main()
{
	while(~scanf("%d %d",&m,&n))
	{
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		memset(f,0,sizeof(f));
		int Max;
		for(int i=1;i<=m;i++)
		{
			Max=-inf;
			for(int j=i;j<=n;j++)
			{
				f[i][j]=f[i-1][j-1];
				f[i][j]=max(f[i][j],f[i][j-1])+a[j];
				f[i][j-1]=Max;				
				Max=max(Max,f[i][j]);			
			}
		}
		printf("%d\n",Max);
	}
	return 0;
}

题意:给你n个数,让你求m个子段和的最大值,子段之间不能相重合。

思路:这道题一开始想的时候我们可以定义f[i][j]:前j个数中当前i个子段和的最大值,那么状态转移方程为:f[i][j]=max(f[i-1][j-1],f[i][j-1])+a[j],但其实这样做空间可能会超限(因为现在HDU进不去,我不好判断),网上找的题解用的都是一维的,所以我就降维。仔细想想,上一层的答案我们可以用一个数组pre记录下来,这样我们就可以做到用一维来解决了。f[j]:当前层数的前i个数的子段和的最大值,那么状态转移方程为f[j]=max(f[j-1],pre[j-1])+a[j]。另外有一点要注意一下pre[j-1]=Max这个式子一定要放在上面两个式子之间,因为你要先取完当前下标的最大值Max后再更新当前下标pre的值,不然肯定会出错的!

B - HDU1029 Ignatius and the Princess IV

code:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e6+10; 
int n,a[N];
int main()
{
	while(cin>>n)
	{
		memset(a,0,sizeof(a));
		int flag=0;
		for(int i=1;i<=n;i++)
		{
			int c;
			cin>>c;
			a[c]++;
			if(a[c]>=((n+1)/2)&&flag==0) {
				cout<<c<<"\n";
				flag=1;
				continue;
			}
		}
	}
	return 0;
}

水题,不过多解释。

C - HDU1069 Monkey and Banana

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200;
struct Node{
	int x,y,z;
}block[200];
int t,n,f[N];
bool cmp(Node a,Node b)
{
	if(a.x!=b.x) return a.x<b.x;
	else return a.y<b.y;
}
int main()
{
	t=1;
	while(cin>>n)
	{
		if(n==0) break;
		
		for(int i=0;i<n;i++)
		{
			int a,b,c;
			cin>>a>>b>>c;
			block[6*i].x=a,block[6*i].y=b,block[6*i].z=c;
			block[6*i+1].x=a,block[6*i+1].y=c,block[6*i+1].z=b;
			block[6*i+2].x=b,block[6*i+2].y=a,block[6*i+2].z=c;
			block[6*i+3].x=b,block[6*i+3].y=c,block[6*i+3].z=a;
			block[6*i+4].x=c,block[6*i+4].y=a,block[6*i+4].z=b;
			block[6*i+5].x=c,block[6*i+5].y=b,block[6*i+5].z=a;
		}
		sort(block,block+6*n,cmp);
		for(int i=0;i<6*n;i++)
		{
			f[i]=block[i].z;
			for(int j=0;j<i;j++)
			{
				if(block[i].x>block[j].x&&block[i].y>block[j].y)
				f[i]=max(f[i],f[j]+block[i].z);	
			}
		}
		int maxn=-1;
		for(int i=0;i<6*n;i++)
		{
			if(maxn<f[i])
			maxn=f[i];
		}
		printf("Case %d: maximum height = %d\n",t++,maxn);
	}
	return 0;
}

题意:有n种类型的矩形方块,每个方块可用无限多个,问最多能叠多高,叠加的条件是上面的方块的长和宽要严格小于下面方块的长和宽。

思路:这道题有点像LIS问题,我们可以把长宽高任意组合,一种类型有6种组合,那么n种类型有6n种组合,之后把这6n种组合存在结构体数组内,按照长度相同时宽度从小到大,长度不相同时长度从小到大排序。状态转移方程为:f[i]=max(f[i],f[j]+block[i].z),不过要加一个判断条件:block[i].x>block[j].x&&block[i].y>block[j].y,然后在6*n种答案里选最大值即可。

D - HDU1074 Doing Homework

code:

#include<iostream>
#include<cstdio>
using namespace std;
const int N=20,inf=0x3f3f3f3f;
struct Node{
	string name;
	int deadline;
	int day;
}course[N];
struct Dp{
	int now;存放当前的时间
	int pre;//存放前一个状态到达当前状态所完成的课程
	int score;//存放当前减少的分数
}dp[1 << N];
void output(int x)
{
	int l=0;
	int ans[N];
	while(x)
	{
		ans[l++]=dp[x].pre;
		x=x-(1 << dp[x].pre);
	}
	for(int i=l-1;i>=0;i--)
	cout<<course[ans[i]].name<<"\n";
}
int main()
{
	int t,n;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=0;i<n;i++)
		{
			cin >> course[i].name >> course[i].deadline >> course[i].day;
		}
		dp[0].now=0;
		dp[0].pre=-1;
		dp[0].score=0;
		for(int i=1;i<(1 << n);i++)
		dp[i].score = inf;
		for(int i=0;i<(1 << n);i++)//枚举所有状态
		{
			for(int j=0;j<=n-1;j++)
			{
				int tmp=(1 << j);
				if(i & tmp) continue;//如果第j门课程已经做了
				int t=i | tmp;//加入第j门课程,到达当前状态
				int time=dp[i].now+course[j].day;//计算当前时间
				int sc=0;
				if(time>course[j].deadline)//如果时间超了就计算要被减去的分数
				sc=time-course[j].deadline;
				sc+=dp[i].score;//跟上一个状态的分数相加
				if(dp[t].score>sc)//看是否能更新
				{
					dp[t].now=time;
					dp[t].pre=j;
					dp[t].score=sc;
				}
			}
		}
		cout<<dp[(1 << n)-1].score<<"\n";//找到所有功课都完成的状态的结果
		output((1 << n)-1);
	}
	return 0;
}

题意:给你n门课程的名字、截止时间和做完这门课程需要花的时间,课程没有在规定时间内做完会扣分,求一种方案使得扣分最少,输出最少扣分和方案。

思路参考了这位博主的代码再加了些自己的看法。这道题我们可以先看数据范围,1<=N<=15,这就提醒我们可以用状态压缩DP来写。1代表写了,0代表没写,用二进制表示课程完成情况,我们可以规定1011代表第1、2、4三门课程完成了,第3门课程没完成,也可以规定1011代表第1,3,4三门课程完成了,第2门课程没完成,但在这里你只能选前者因为题目中规定了字母序最小的,如果你要按后面的来写,我认为是写不出来的,答案一定会错!还有一点要注意,博主的代码循环跟我的代码循环刚好相反:for(int j=n-1;j>=0;j–),在这里循环的顺序不会影响最终结果,这是我思考了很长时间得出的结论,影响最终结果的是你对1011的规定是前者还是后者

E - Hdu1087 Super Jumping! Jumping! Jumping!

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1005;
ll ans,dp[N],a[N],n;
int main()
{
	while(cin>>n)
	{
		if(n==0) break;
		for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
		ans=-1;
		for(int i=1;i<=n;i++)
		{
			dp[i]=a[i];
			for(int j=1;j<=i;j++)
			{
				if(a[i]>a[j])
				{
					dp[i]=max(dp[j]+a[i],dp[i]);
				}
			}
			ans=max(ans,dp[i]);
		}
		cout<<ans<<"\n";
	}
	
	return 0;
}

题意:就是找一个上升子序列,使他的和最大。

思路:可以类比求最长上升子序列的题来写。

F - Hdu1114 Piggy-Bank

code:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int t;
int e,f,n,v[505],w[505],dp[10005];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		cin>>e>>f>>n;
		for(int i=1;i<=n;i++)
		scanf("%d %d",&v[i],&w[i]);
		
		dp[0]=0;
		for(int i=1;i<=f-e;i++) dp[i]=0x3f3f3f3f;
		
		for(int i=1;i<=n;i++)
    	for(int j=w[i];j<=f-e;j++)
    	{
        	dp[j]=min(dp[j],dp[j-w[i]]+v[i]);
    	}
    	if(dp[f-e]!=0x3f3f3f3f)
    	cout<<"The minimum amount of money in the piggy-bank is "<<dp[f-e]<<"."<<endl;
    	else cout<<"This is impossible."<<endl;
	}
	return 0;
}

题意:给定一个重量为f-e的背包和n种类型的物品,每个物品有重量和价值,每种物品有无限多个,问背包恰好放满且背包里物品总价值最低为多少。

思路:基本跟完全背包问题相同,就是要把max改为min,其他代码相同。

G - HDU1176 免费馅饼

1.code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,dp[100005][20];
int main()
{
	while(~scanf("%d",&n))
	{
		if(n==0) break;
		int maxt=-1;
		memset(dp,0,sizeof(dp));
		for(int i=0;i<n;i++)
		{
			int t,p;
			scanf("%d%d",&p,&t);
			dp[t][p]++;
			maxt=max(maxt,t);
		}
		for(int i=maxt;i>=0;i--)
		{
			for(int j=0;j<11;j++)
			{
				dp[i][j]+=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]));
			}
		}
		printf("%d\n",dp[0][5]);
	}
	return 0;
}

2.code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,dp[100005][20];
int main()
{
	while(~scanf("%d",&n))
	{
		if(n==0) break;
		int maxt=-1;
		memset(dp,0,sizeof(dp));
		for(int i=0;i<n;i++)
		{
			int t,p;
			scanf("%d%d",&p,&t);
			dp[t][p]++;
			maxt=max(maxt,t);
		}
		for(int i=1;i<=maxt;i++)
		{
			for(int j=0;j<11;j++)
			{
				if(j>=1)
				dp[i][j]+=max(dp[i-1][j],max(dp[i-1][j-1],dp[i-1][j+1]));
				else dp[i][j]+=max(dp[i-1][j],dp[i-1][j+1]);
			}
		}
		int ans=-1;
		for(int j=0;j<11;j++)
		ans=max(ans,dp[maxt][j]);
		printf("%d\n",ans);
	}
	return 0;
}

题意:一个人在0~10这11个位置捡馅饼,一开始他在5这个位置,第1s时他能捡4,5,6这3个位置的馅饼中的一种。馅饼会在T时间掉落在x这个位置,给你n个馅饼的x和T,问最多能捡几个馅饼。馅饼可能会在同一时间同一位置掉落多个馅饼

思路:定义dp[i][j]:截止到第j秒i位置捡到的馅饼数量的最大值,我们有两种思路,你可以从前往后算,也可以从后往前算(时间上),如果是从后往前算,就是第一种代码,答案就是dp[0][5]了,反之就是第二种代码,要在最大时间的0~10这11个位置取最大值。

H - HDU1260 Tickets

code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=2005;
int s[N],d[N],dp[N];
int main()
{
	int n;
	cin>>n;
	while(n--)
	{
		int k;
		cin>>k;
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=k;i++)
		cin>>s[i];
		for(int i=2;i<=k;i++)
		cin>>d[i];
		d[1]=s[1];
		for(int i=2;i<=k;i++)
		{
			dp[i]=min(dp[i-1]+s[i],dp[i-2]+d[i]);
		}
		int ss=d[k]%60;
		int m=d[k]/60%60;
		int h=d[k]/3600;
		h+=8;
		if(h<=12)
		printf("%02d:%02d:%02d am\n",h,m,ss);
		else printf("%02d:%02d:%02d pm\n",h-12,m,ss);
		
	}
	return 0;
}

题意:给你N个测试样例,每个样例会给出k个人买票所需的时间以及k-1个两个相邻的人买票总共需要的时间,问这k个人买票所需的最少时间。

思路:线性DP,dp[i]表示第1个人到第i个人买票花的最少时间,那么状态转移方程为:dp[i]=min(dp[i-1]+s[i],dp[i-2]+d[i]),dp[k]就是答案,然后根据样例输出即可。

I - Hdu1257 最少拦截系统

code:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int a[N],dp[N];
int main()
{
	int n;
	while(cin>>n)
	{
		for(int i=0;i<n;i++) {
			cin>>a[i];
			dp[i]=1;
		}
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<i;j++)
			{
				if(a[j]<a[i])
				{
					dp[i]=max(dp[i],dp[j]+1); 
				}
			}
		}
		int ans=0;
		for(int i=0;i<n;i++)
		ans=max(ans,dp[i]);
		cout<<ans<<"\n";
	}
	return 0;
}

题意:不解释,看题目,很直白。

思路:这道题一开始我以为是求多次最长下降子序列,每次求完就删掉,直到全部删完,输出次数即可,但后来发现这样做不太现实,做不出来。想过一个思路,发现如果后面导弹的高度比前面的高,就要换过一个导弹系统,于是我们就可以按照最长上升子序列的代码来写即可得出答案。

J - HDU1160 FatMouse’s Speed

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct Node{
	int w,s,id;
}m[1005];
struct DP{
	int num,pre;
}dp[1005];
bool cmp(Node a,Node b)
{
	if(a.w!=b.w) return a.w<b.w;
}
void output_path(int x)
{
	if(dp[x].pre!=-1)
	output_path(dp[x].pre);
	printf("%d\n",m[x].id);
} 
int main()
{
	int t=1;
	while(~scanf("%d %d",&m[t].w,&m[t].s))
	{
		m[t].id=t;
		++t;
	}
	sort(m+1,m+1+t,cmp);
	int ans=0,x=-1;
	
	for(int i=1;i<=t;i++)
	dp[i].num=1,dp[i].pre=-1;
	
	for(int i=1;i<=t;i++)
	{
		for(int j=1;j<i;j++)
		{
			if((m[j].w<m[i].w)&&(m[j].s>m[i].s))
			{
				if(dp[i].num<dp[j].num+1)
				{
					dp[i].num=dp[j].num+1;
					dp[i].pre=j;
				}				
			}
		}
		if(ans<dp[i].num)
		ans=dp[i].num,x=i;
	}
	printf("%d\n",ans);
	output_path(x);
	
	return 0;
}

题意:给你一定数量的老鼠,每个老鼠有体重和速度两个属性,需要求一个老鼠序列,使得这个序列的长度最长,输出序列的长度和路径。序列要符合老鼠的体重越大,速度越小的结论。

思路:这道题一开始不知道怎么输出路径,看了题解之后才恍然大悟。就是要在dp这个结构体数组中加一个pre这个变量来记录路径,还要记录序列最长的最后一个元素的下标就可以解决了。我的做法是用一个结构体数组m来记录体重,速度和下标,然后对这个数组按照体重从小到大排序,然后在循环中进行比较,更新答案,输出路径用一个递归函数来解决。

K - POJ1015 Jury Compromise

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
int dp[21][805];
//现用dp(j, k)表示,取j 个候选人,使其辩控差为k 的所有方案中,
//辩控和最大的那个方案(该方案称为“方案f(j, k)”)的辩控和
vector<int> path[21][805];//记录路径  path[i][j] 取i个人  他们的差为j时候的路径
int main()
{
	int n,m,t=1;
	while(~scanf("%d %d",&n,&m))
	{
		if(n==0&&m==0) break;
		int sub[210],_plus[210];
		for(int i=0;i<n;i++)
		{
			int d,p;
			scanf("%d %d",&d,&p);
			sub[i]=d-p;
			_plus[i]=d+p;
		}
		for(int i=0;i<m;i++)
		{
			for(int j=0;j<805;j++)
			{
				path[i][j].clear();
			}
		}
		memset(dp,-1,sizeof(dp));
		int fix=20*m;
		dp[0][fix]=0;
		for(int k=0;k<n;k++)
		{
			for(int i=m-1;i>=0;i--)
			{
				for(int j=0;j<2*fix;j++)
				{
					if(dp[i][j]>=0)
					{
						if((dp[i+1][j+sub[k]])<=(dp[i][j]+_plus[k]))
						{
							dp[i+1][j+sub[k]]=dp[i][j]+_plus[k];
							path[i+1][j+sub[k]]=path[i][j];
							path[i+1][j+sub[k]].push_back(k);
						}
					}
				}
			}
		}
		int i;
		for(i=0;dp[m][fix+i]==-1&&dp[m][fix-i]==-1;i++);
		int temp = (dp[m][fix+i] > dp[m][fix-i]) ? i : -i;
		
		int sumD=(dp[m][fix+temp]+temp)/2;
		int sumP=(dp[m][fix+temp]-temp)/2;
		
		printf("Jury #%d\n",t++);
		printf("Best jury has value %d for prosecution and value %d for defence:\n",sumD,sumP);
		for(int i=0;i<m;i++)
		printf(" %d",path[m][fix+temp][i]+1);
		printf("\n\n");
	}
	return 0;
}

题意:从n个人中选出m个人,选法为控方满意度之和s1与辩方满意度之和s2的差的绝对值最小,若有多种方案,则选择控方满意度之和s1与辩方满意度之和s2的和最大的一组,先输出这是第几组数据,下一行分别输出选择出来的方案中的控方满意度之和s1与辩方满意度之和s2。最后一行输出选择的人的编号,按从小到大的顺序排列。

思路:这道题不仅本身就很难,题目还贼难理解,英文的楞是看了4,5遍没看懂题意,翻译了一下才看懂了。一开始没啥思路,参考了一些大佬的思路。首先定义dp[j][k]:取j 个候选人,使其辩控差为k 的所有方案中,辩控和最大的那个方案的辩控和,那么状态转移方程为:dp[i+1][j+sub[k]]=dp[i][j]+_plus[k]。具体思路见代码。
有几点需要注意
1.不能直接记录差的绝对值!因为在后面循环遍历中这样做会覆盖很多答案,导致出错。
2.在写的时候是以dp[0][fix]作为起始点的,这是为了防止差是负数的情况

L - POJ1458 Common Subsequence

code:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e3+10;
int dp[N][N];
int main()
{
	char a[N],b[N];
	while(~scanf("%s %s",a,b))
	{
		int n=strlen(a);
		int m=strlen(b);
		memset(dp,0,sizeof(dp));
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<m;j++)
			{
				if(a[i]==b[j])
				dp[i+1][j+1]=dp[i][j]+1;
				else dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
			}
		}
		printf("%d\n",dp[n][m]);
	}
	return 0;
}

题意:给定两个序列,求最长公共子序列的长度。

思路:经典LCS模板题。

M - POJ1661 Help Jimmy

code:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1005,INF=0x3f3f3f3f;
int dp[N][5];
int n,x,y,Max;
struct Plat{
	int x1,x2,high;
}plat[N];
bool cmp(Plat a,Plat b)
{
	return a.high<b.high;
}
void LeftMove(int i)
{
	int k=i-1;
	while((k>0)&&(plat[i].high-plat[k].high<=Max))
	{
		if((plat[i].x1>=plat[k].x1)&&(plat[i].x1<=plat[k].x2))
		{
			dp[i][0]=plat[i].high-plat[k].high+min(plat[i].x1-plat[k].x1+dp[k][0],plat[k].x2-plat[i].x1+dp[k][1]);
			return;
		}
		else --k;
	}
	if(plat[i].high-plat[k].high>Max)
	dp[i][0]=INF;
	else dp[i][0]=plat[i].high;
}
void RightMove(int i)
{
	int k=i-1;
	while((k>0)&&(plat[i].high-plat[k].high<=Max))
	{
		if((plat[i].x2>=plat[k].x1)&&(plat[i].x2<=plat[k].x2))
		{
			dp[i][1]=plat[i].high-plat[k].high+min(plat[i].x2-plat[k].x1+dp[k][0],plat[k].x2-plat[i].x2+dp[k][1]);
			return;
		}
		else --k;
	}
	if(plat[i].high-plat[k].high>Max)
	dp[i][1]=INF;
	else dp[i][1]=plat[i].high;
}
int ShortestTime()
{
	for(int i=1;i<=n+1;i++)
	{
		LeftMove(i);
		RightMove(i);
	}
	return min(dp[n+1][0],dp[n+1][1]);
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d%d",&n,&x,&y,&Max);
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%d",&plat[i].x1,&plat[i].x2,&plat[i].high);
		}
		plat[n+1].x1=x;
		plat[n+1].x2=x;
		plat[n+1].high=y;
		plat[0].x1=-20000;
		plat[0].x2=20000;
		plat[0].high=0;
		sort(plat,plat+n+2,cmp);
		printf("%d\n",ShortestTime());
	}
	return 0;
}

题意:题目简单直白,见题目。

思路:线性DP,定义dp[i][j]:j=0时代表向左走,j=1时代表向右走,把老鼠也看成一个平台,从第i个平台向左走或向右走到达地面的最短时间。那么答案就是max(dp[n+1][0],dp[n+1][1])。

N - POJ2533 Longest Ordered Subsequence

code:

#include<iostream>
#include<algorithm>
using namespace std;
int dp[1005],a[1005];
int main()
{
	int n,ans=-1;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		dp[i]=1;
		for(int j=1;j<i;j++)
		{
			if(a[i]>a[j])
			{
				dp[i]=max(dp[i],dp[j]+1);
			}
		}
		ans=max(ans,dp[i]);
	}
	printf("%d",ans);
	return 0;
}

题意:给出n个数,求最长上升子序列的数量。

思路:经典LIS模板题。

O - POJ3186 Treats for the Cows

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2005;
int a[N],dp[N][N];

int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int len=1;len<=n;len++)
	{
		for(int i=1,j=i+len-1;i<=n;i++,j++)
		{
			dp[i][j]=max(dp[i+1][j]+a[i]*(n-len+1),dp[i][j-1]+a[j]*(n-len+1));
		}
	}
	printf("%d",dp[1][n]);
	return 0;
}

题意:双端队列里取数,每次取数都乘上它取出时的序列号,问和最大为多少。

思路:dp[i][j]表示序列从i~j的所求值。经典区间DP,不过是倒着推,难点在于当前区间天数的表示。

P - HDU1078 FatMouse and Cheese

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int a[105][105];
bool vis[105][105];
int ans[105][105],dx[]={1,-1,0,0},dy[]={0,0,1,-1};
int n,k;
void dfs(int x,int y)
{
	
	for(int i=0;i<4;i++)
	{
		for(int j=1;j<=k;j++)
		{
			int xx=x+dx[i]*j;
			int yy=y+dy[i]*j;
			if(a[x][y]<a[xx][yy]&&vis[xx][yy]==0&&xx>=0&&xx<n&&yy>=0&&yy<n)
			{
				vis[xx][yy]=1;
				ans[xx][yy]=max(ans[xx][yy],ans[x][y]+a[xx][yy]);
				dfs(xx,yy);
				vis[xx][yy]=0;
			}
		}
	}
}
int main()
{
	
	while(~scanf("%d%d",&n,&k))
	{
		if(n==-1&&k==-1) return 0;
		memset(vis,0,sizeof(vis));
		for(int i=0;i<n;i++)
			for(int j=0;j<n;j++)
			{
				scanf("%d",&a[i][j]);
				ans[i][j]=a[i][j];
			}
		
		dfs(0,0);
		int anss=-1;
		for(int i=0;i<n;i++)
			for(int j=0;j<n;j++)
			{
				anss=max(anss,ans[i][j]);
			}
		printf("%d\n",anss);
	}
	return 0;
}

题意:老鼠一开始在(0,0)的位置,每个位置都有一定数量的食物,老鼠一次最多走k步,规定走过的位置的食物量要越来越大,问老鼠最多能吃的食物量。

思路:挺简单的,记忆化搜索DP。

Q - HDU285 Phalanx

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1005;
char st[maxn][maxn];
int dp[maxn][maxn];
int main()
{
	int n,ans,t1,t2;
	while(scanf("%d",&n) && n)
	{
		ans=0;
		for(int i=0;i<n;i++)
		scanf("%s",st[i]);
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				if(i==0||j==n-1)
				dp[i][j]=1;
				else{
					t1=i,t2=j;
					while(t1>=0&&t2<n)
					{
						if(st[t1][j]==st[i][t2])
						t1--,t2++;
						else break;
					}
					int t=i-t1;
					dp[i][j] = min(i-t1, dp[i-1][j+1]+1);
				}
				ans=max(ans,dp[i][j]);
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

题意:给定一个n行n列的字符矩阵。求这个矩阵的最大对称子矩阵的大小。在这里的对称指的是关于左下和右上相连的对角线对称。

思路:我们定义dp[i][j]:0~i, j~n-1形成的矩阵的最大对称子矩阵的大小。
在这里插入图片描述
我们可以先将dp[i][j]全部初始化为1,也可以像上面代码一样只初始化第一行和第n-1列为1,后面的具体操作可结合代码和图片来理解。

R - POJ3616 Milking Time

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{
	int l,r,e;
}a[1005];
int f[1005];
bool cmp(Node a,Node b)
{
	return a.r<b.r;
}
int main()
{
	int N,M,R;
	scanf("%d%d%d",&N,&M,&R);
	for(int i=0;i<M;i++)
	{
		scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].e);
		a[i].r+=R;//预处理
	}
	sort(a,a+M,cmp);//按右端点从小到大排序
	int k;
	k=0;
	for(int i=1;i<=N+R;i++)
	{
		f[i]=f[i-1];
		while(a[k].r==i) f[i]=max(f[i],f[a[k].l]+a[k++].e);//可能有多个值相等,要用while
	}
	printf("%d",f[N+R]);
	return 0;
}

题意:给了M个时间段和产奶量,要在规定的N个小时内分配合理时间使总产奶量最大。注意奶牛产完奶后要有R个小时休息后才能继续工作。

思路:见代码和注释。

S - POJ3666 Making the Grade

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2005,INF=0x3f3f3f3f;
int dp[N][N],a[N],b[N],n;
void solve()
{
	for(int i=1;i<=n;i++)
	{
		int minn=INF;
		for(int j=1;j<=n;j++)
		{
			int cost=abs(a[i]-b[j]);
			minn=min(minn,dp[i-1][j]);//dp[i-1][j]指的是前i-1个数最大值为b[j]时的最小代价
			dp[i][j]=cost+minn;
		}
	}
	int ans=INF;
	for(int i=1;i<=n;i++)
	ans=min(ans,dp[n][i]);
	printf("%d",ans);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+1+n);
	solve();
	
	return 0;
}

这道题一开始我没啥思路,后来看了题解才知道是线性DP+离散化。可以参考这位博主的思路

题意:给出一个长度为 n 的序列,要求使序列变为非递增或非递减序列,问花费的最少代价。在之后的做题中统一将其改为非递减序列。原因后面会解释。

思路:题目显然是 LIS 问题的变种,线性 DP 无疑

对于长度为 i 的序列,其最优解只与两个值有关,一个是这个序列处理的代价,另一个则是这个序列处理后的尾部的值。那么显然,我们希望序列处理的代价越小越好,而序列处理后的尾部的值越小越好,因为当序列处理完毕后,尾部的值是这个序列的最大值,其值越小,第 i+1 个数不花任何代价直接接在其后面的可能性就更大。

设 dp[i][j] 为长度为前 i 个数构成的序列,且处理完最大值为 j 所花费的相应代价,那么可以得出状态转移方程:

dp[i][j]=abs(j-a[i])+min(dp[i-1][k]),k<=j,其中 abs(j-a[i]) 代表处理的代价,min(dp[i-1][k]) 代表前 i-1 个数构成的序列最大值为 k 时所花费的最小代价

注意到 j 最大可达到 1,000,000,000,那么显然枚举的话一定会 TLE,而 n 的大小最大只有 2000,那么使用离散化的思想,先对序列 a[i] 进行处理,即:

对于长度为 n 的序列,可以发现序列中的某个数 a[i],无论怎么变化,最小代价一定是序列中的某个数与当前这个数 a[i] 的差,因此我们可以建立一个备份数组 b[i],将原来的 j-a[i] 转变为 b[j]-a[i],也即 abs(b[j]-a[i]),然后再进行动态规划

最后,再从 dp[n][1~n] 中寻找最小值即可。

有几点我的看法:

1.我感觉博主原代码中的注释有点问题,所以代码我改了一下,dp[i][j]在离散化之后的意义就变了,变为前i个数最大值为b[j]时的最小代价
2.解释下为什么统一将序列改为非递减,我认为在原文中他有两个方向可以走,你从左边开始走序列为非递减,那么你从右边开始走序列就为非递增,所以我们可以统一将序列改为非递减。
3.为什么对于长度为 n 的序列,可以发现序列中的某个数 a[i],无论怎么变化,最小代价一定是序列中的某个数与当前这个数 a[i] 的差?这点很好解释,当你要改变当前的值时,你所替换的值要么是与前一个改变后的值相等,要么与后一个未改变的值相等,毕竟是要使尾部的值越小越好,这样改动才能使和最小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值