zoukankan      html  css  js  c++  java
  • 对一些简单动态规划问题的统一讲解

    (DP-20)

    更好的阅读体验
    最近要写计划,当然,就是讲自己的弱项之类的写上去,脑子一抽写了个九月份之前(20)(DP)题,现在就只能橙黄相伴了.....现在是把做过的DP的题写一下题解报告吧......

    1.又上锁妖塔(Link

    妖塔的建造很特别,塔总共有(n)层,但是高度却不相同,这造成了小(A)爬过每层的时间也不同.小(A)会用仙术,每用一次可以让他向上跳一层或两层,但是每次跳跃后小(A)都将用完灵力,必须爬过至少一层才能再次跳跃(你可以认为小(A)需要跳两次一层才休息),小(A)想用最短的时间爬到塔顶,可是他不能找到时间最短的方案,所以请你帮他找到一个时间最短的方案让他爬到塔顶,小(A)只关心时间,所以你只要告诉他最短时间是多少就可以了.你可以最后跳到塔外即超过塔高.

    输入输出格式

    输入格式:
    第一行一个数(n (n<=1000000)),表示塔的层数.

    第二行(n)个数((<=100)),表示从下往上每层的高度.

    输出格式:
    一个数,表示最短时间.

    首先,按照DP的常规思路,我们先找到子问题:
    (f[i])为爬上第(i)层的最短时间。那么我们可以很容易地确定出子问题:

    1.是从(i-1)层爬上第(i)层的
    2.是用魔法飞上第(i)层的

    我们知道爬上第(i)层肯定是要从第(i-1)层,即(color{red}{f[i]=f[i-1]+high[i]}).
    然后至于第二种情况,我们又要分两种情况:1.从(i-2)层跳上来,2.从(i-3)层跳上来,时间要取最小值,那么就是(color{red}{f[i]=min(f[i-2]+high[i],f[i-3]+high[i])})
    下面是代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 10010
    using namespace std;
    int n,dp[MAXN];
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	scanf("%d",&dp[i]);	
    	for(int i=1;i<=n+1;i++)
    	dp[i]=min(dp[i-1],min(dp[i-2],dp[i-3]))+dp[i];
    	//对于所有的情况,直接取min就好了。
    	printf("%d",dp[n+1]);
    	return 0;
    }
    

    2.编辑距离(Link

    (A)(B)是两个字符串。我们要用最少的字符操作次数,将字符串(A)转换为字符串(B)。这里所说的字符操作共有三种:

    1、删除一个字符;
    2、插入一个字符;
    3、将一个字符改为另一个字符;
    (!皆为小写字母!)

    输入输出格式
    输入格式:
    第一行为字符串(A);第二行为字符串(B);字符串(A)(B)的长度均小于(2000)
    输出格式:
    只有一个正整数,为最少字符操作次数。

    子问题确定:对于一个状态(i)我们只有四种操作方式:删除、插入、改变、不变。所以状态(i)就是由前一个状态进行这四步操作转化过来的,所以我们的自问题也就是(4)个。
    首先我们设(dp[i][j])为字符串(A)的前(i)个字符变为字符串(B)的前(j)个字符需要多少步,然后最后的答案显然就是(dp[lenA][lenB]);
    那么再根据子问题分开来谈状态转移方程。
    (1.)删除。我们直接选择删除最后一个,那么也就变成了(A)的前(i-1)个字符变为(B)的前(j)个字符需要多少步,删除需要步数(+1)。即(color{red}{dp[i][j]=dp[i-1][j]+1});
    (2.)添加。我们选择在(A)的最后面添加一个字符,那么肯定是为了使最后一个字符相匹配,那么最后一个字符也就匹配上可以删去,那么我们可以理解为(A+1-1)(B-1),那么就是(j-1)咯。同样,添加也需要步数(+1)(color{red}{dp[i][j]=dp[i][j-1]+1});
    (3.)替换,我们替换(A)字符串的最后一个字符为(B)的最后一个字符,那么(A)(B)的最后一位显然必定也是匹配上可以删除了,那么(i)(j)都要减一,同样步数要(+1),即:(color{red}{dp[i][j]=dp[i-1][j-1]+1});
    (4.)不变。字符串(A)(B)的最后一个字符都相等,那么自然不要变咯,同样删除最后一个字符,但是步数并不用(+1),因为我们并没有进行操作,那么方程就是(color{red}{dp[i][j]=dp[i-1][j-1]});
    然后最后的状态转移方程就是:

    (color{red}{dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1)+b)})

    当然,这个(b)是要看(A)(B)的最后一个字符是不是相等。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 2010
    using namespace std;
    string a,b;
    char s1[MAXN],s2[MAXN];
    int f[MAXN][MAXN];
    int DP(int i,int j){
    	if(f[i][j]!=-1) return f[i][j];
    	if(i==0) return f[i][j]=j;
    	if(j==0) return f[i][j]=i;
    	int ken=1;
    	if(s1[i]==s2[j]) ken=0;
    	return f[i][j]=min(min(DP(i-1,j)+1,DP(i,j-1)+1),DP(i-1,j-1)+ken);
    }
    int main(){
    	cin>>a>>b;
    	int len1=a.length();
    	int len2=b.length();
    	memset(f,-1,sizeof(f));
    	for(int i=1;i<=len1;i++)
    	s1[i]=a[i-1];
    	for(int i=1;i<=len2;i++)
    	s2[i]=b[i-1];
    	DP(len1,len2);
    	printf("%d",f[len1][len2]);
    	return 0;
    }
    

    接苹果(Link

    很少有人知道奶牛爱吃苹果。农夫约翰的农场上有两棵苹果树(编号为(1)(2)), 每一棵树上都长满了苹果。奶牛贝茜无法摘下树上的苹果,所以她只能等待苹果 从树上落下。但是,由于苹果掉到地上会摔烂,贝茜必须在半空中接住苹果(没有人爱吃摔烂的苹果)。贝茜吃东西很快,她接到苹果后仅用几秒钟就能吃完。每一分钟,两棵苹果树其中的一棵会掉落一个苹果。贝茜已经过了足够的训练, 只要站在树下就一定能接住这棵树上掉落的苹果。同时,贝茜能够在两棵树之间 快速移动(移动时间远少于(1)分钟),因此当苹果掉落时,她必定站在两棵树其中的一棵下面。此外,奶牛不愿意不停地往返于两棵树之间,因此会错过一些苹果。苹果每分钟掉落一个,共(T(1<=T<=1000))分钟,贝茜最多愿意移动(W(1<=W<=30)) 次。现给出每分钟掉落苹果的树的编号,要求判定贝茜能够接住的最多苹果数。 开始时贝茜在(1)号树下。

    输入输出格式

    输入格式:
    第一行(2)个数,(T)(W)。接下来的(t)行,每行一个数,代表在时刻(t)苹果是从(1)号苹果树还是从(2)号苹果树上掉下来的。

    输出格式:
    对于每个测试点,输出一行,一个数,为奶牛最多接到的苹果的数量。

    照常,我们首先找出子问题。
    (color{red}{f[i][j][1]})表示最多走i步,时刻(j)处于第(1)棵树下的最多苹果数
    (color{red}{f[i][j][2]})表示最多走i步,时刻(j)处于第(2)棵树下的最多苹果数
    子问题一共分(4)种情况。
    (1.)如果从第(1)棵树掉下来,并且最后一次移动在时刻(j),那么(color{red}{dp[i][j][1]=max(dp[i][j-1][1]+1,dp[i-1][j-1][2]+1)});
    (2.)如果从第(1)棵树掉下来,并且最后一次移动不在时刻(j),那么(color{red}{dp[i][j][2]=max(dp[i][j-1][2],dp[i-1][j-1][1])});
    (3.)如果从第(2)棵树掉下来,并且最后一次移动在时刻(j),那么(color{red}{dp[i][j][1]=max(dp[i][j-1][1],dp[i-1][j-1][2])});
    (4.)如果从第(2)棵树掉下来,并且最后一次移动在不时刻(j),那么(color{red}{dp[i][j][2]=max(dp[i][j-1][2]+1,dp[i-1][j-1][1]+1)});
    我们知道初始时刻我们在第(1)棵树下,那么从头开始如果一直是从(1)掉下,那么我们肯定就是一直不用动,(dp[0][j][1])就会一直++,知道出现第一个从(2)掉下的苹果为止,而这个(dp[0][j][1])就是初始化。
    那么最后的答案就是(color{red}{max(dp[w][t][1],f[w][t][2])});

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 1010
    using namespace std;
    int a[MAXN],dp[MAXN][MAXN][2];
    int n,w;
    int main(){
    	scanf("%d%d",&n,&w);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&a[i]);
    		a[i]--;
    	}
    	for(int i=1;i<=n;i++){
    		dp[i][0][0]=dp[i-1][0][0]+(a[i]^1);
    		for(int j=1;j<=w;j++){
    			dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j-1][1])+(a[i]^1);
    			dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0])+a[i];
    		}	
    	}	int ans=0;
    	for(int i=1;i<=w;i++)
    	ans=max(ans,max(dp[n][i][1],dp[n][i][0]));
    	printf("%d",ans); return 0;
    }
    

    4.领地选择(Link

    题目描述

    作为在虚拟世界里统帅千军万马的领袖,小Z认为天时、地利、人和三者是缺一不可的,所以,谨慎地选择首都的位置对于小T来说是非常重要的。

    首都被认为是一个占地C*C的正方形。小Z希望你寻找到一个合适的位置,使得首都所占领的位置的土地价值和最高。

    输入输出格式

    输入格式:
    第1行:三个正整数N,M,C,表示地图的宽和长以及首都的边长。
    第2~N+1行:第i+1行包含M个整数,表示了地图上每个地块的价值。价值可能为负数。
    输出格式:
    一行,两个整数X、Y,表示首都左上角的坐标。

    这个题其实可以用二维前缀和来做,也不算是DP了吧~。
    首先是预处理:
    picture1
    我们设f[i][j]为以((i,j))为右下角,((1,1))为左上角的矩阵的所有点的点权和,那么我们可以知道这是个二维前缀和了,需要用(A)点本身的价值+红色区域的矩形大小+灰色区域的矩形大小-棕色区域的矩阵大小(因为加了两遍),那么我们可以得出方程(color{red}{f[i][j]=a[i][j]+f[i-1][j]+f[i][j-1]-f[i-1][j-1]})
    处理完二维前缀和之后,接着(for)一遍整个矩形,当然,(for)横向的时候,我们只需要(for)(n-c+1)就可以,因为再(for)下去也不可能的到边长为(c)的矩形了,同理,(for)竖向的时候,我们也只(for)(m-c+1)即可。这个时候我们枚举的是左上角的坐标,那么右下角的坐标就是((i+c-1,j+c-1))
    接下来就是取(max)操作了,我们用(color{red}{f[i-c+1][j-c+1]=f[i-1][j-c+1]-f[i-c+1][j-1]+f[i-1][j-1]})来取(max),其实就是上一步的操作反过来而已。然后在更新(ans)的时候也记录一下((i,j))就可以了。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 1010
    #define INF 0x7fffffff
    using namespace std;
    int n,m,c,f[MAXN][MAXN];
    int a[MAXN][MAXN];
    int main(){
    	scanf("%d%d%d",&n,&m,&c);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			scanf("%d",&a[i][j]);
    			//输入
    	for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                f[i][j]=a[i][j]+f[i-1][j]+f[i][j-1]-f[i-1][j-1];
                //求二维前缀和
        int ans=-INF,mi,mj;
    	for(int i=1;i<=n-c+1;i++)
        	for(int j=1;j<=m-c+1;j++){
        			int x=i+c-1; int y=j+c-1;
        			if(ans<f[x][y]-f[i-1][y]-f[x][j-1]+f[i-1][j-1]){
        				ans=f[x][y]-f[i-1][y]-f[x][j-1]+f[i-1][j-1];
        				//取max操作
        				mi=i; mj=j;
    				}
    			}
    	printf("%d %d",mi,mj);
    	return 0;
    }
    

    5.球迷购票问题(Link

    题目背景
    按售票处规定,每位购票者限购一张门票,且每张票售价为50元。在排成长龙的球迷中有N个人手持面值50元的钱币,另有N个人手持面值100元的钱币。假设售票处在开始售票时没有零钱。试问这2N个球迷有多少种排队方式可使售票处不致出现找不出钱的尴尬局面。
    题目描述
    例如当n=2是,用A表示手持50元面值的球迷,用B表示手持100元钱的球迷。则最多可以得到以下两组不同的排队方式,使售票员不至于找不出钱。
    第一种:A A B B
    第二种:A B A B
    [编程任务]
    对于给定的n (0≤n≤20),计算2N个球迷有多少种排队方式,可以使售票处不至于找不出钱。
    输入输出格式
    输入格式:
    一个整数,代表N的值
    输出格式:
    一个整数,表示方案数

    首先我们知道,购票处可以找的钱全部来自与之前收到的(50)元钱,而每一个(100)元都需要找钱,在这里是可以用卡特兰数来做的,但是毕竟是(DP)题解报告,我们运用一种(DP)的思路进行解题。

    我们设(dp[i][j])为到了第(i)个人,手里面现在有(j)张可以找钱的(50),的到这种情况的方案数,那么考虑子问题,每一个(dp[i][j])都可以由上一个拿了一张(50)的或者(100)的转化而来,拿了(100)了的话就是(color{red}{dp[i-1][j-1]}),(因为拿了(100)就要找出(50)出去,所以(j)也要-1),如果拿了(50)的就是(color{red}{dp[i-1][i+1]}),并且要注意是+=不是=。那么在这里我们不能单纯的取(min)或者是(max),我们需要有一步判断,首先由于是(2·n)个人,所以(i)从1(for)(2·n)。j是从(0for)(n),并且还要有(j<i),因为前(i)个人里面最多有(i)(50)。当(j>1),即可以找钱的时候,我们就由(100)元转移而来,当(j<i)的时候我们就由(50)转移而来。最后输出(dp[n*2][0])即可.

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 1010
    #define INF 0x7fffffff
    #define ll long long
    using namespace std;
    ll n,dp[MAXN][MAXN];
    int main(){
    	scanf("%lld",&n);
    	dp[0][0]=1;
    	for(int i=1;i<=2*n;i++){
    		for(int j=0;j<=n&&j<=i;j++){
    			if(j>=1) dp[i][j]+=dp[i-1][j-1];
    			if(j<=i) dp[i][j]+=dp[i-1][j+1];
    		}
    	}
    	printf("%lld",dp[2*n][0]);
    	return 0;
    }
    

    6.最大正方形(Link

    题目描述
    在一个(n*m)的只包含0和1的矩阵里找出一个不包含(0)的最大正方形,输出边长。
    输入输出格式
    输入格式:
    输入文件第一行为两个整数(n,m(1<=n,m<=100)),接下来(n)行,每行(m)个数字,用空格隔开,(0)(1).
    输出格式:
    一个整数,最大正方形的边长

    我们设(f[i][j])为以((i,j))为右下角,可以构成的最大正方形的边长,那么只有(a[i][j]==1)的时候,我们才将他左右正方形的右下角进行处理,那么对于一个已经确定的(f[i][j]),如果(f[i][j]==x),则表明(i)向上(x)个节点,(j)向左(x)个节点的正方形中所有的点都是(1),对于一个没有确定的(f[i][j]),我们已知(color{blue}{f[i-1][j],f[i][j-1],f[i-1][j-1]})的值,那么我们可以得到状态转移方程:
    (color{red}{if(a[i][j]==1) f[i][j]=min(min(f[i][j-1],f[i-1][j]),f[i-1][j-1])+1;})

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN  110
    using namespace std;
    int n,m,f[MAXN][MAXN];
    bool map[MAXN][MAXN];
    int ans=0;
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++){
    		scanf("%d",&map[i][j]);
    		if(map[i][j]==1)
    			f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
    		ans=max(f[i][j],ans);
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    7.奶牛的零食(Link

    约翰经常给产奶量高的奶牛发特殊津贴,于是很快奶牛们拥有了大笔不知该怎么花的钱.为此,约翰购置了(N(1≤N≤2000))份美味的零食来卖给奶牛们.每天约翰售出一份零食.当然约翰希望这些零食全部售出后能得到最大的收益.这些零食有以下这些有趣的特性:
    零食按照(1)..(N)编号,它们被排成一列放在一个很长的盒子里.盒子的两端都有开口,约翰每天可以从盒子的任一端取出最外面的一个.
    与美酒与好吃的奶酪相似,这些零食储存得越久就越好吃.当然,这样约翰就可以把它们卖出更高的价钱.
    每份零食的初始价值不一定相同.约翰进货时,第(i)份零食的初始价值为(Vi(1≤Vi≤1000))
    第i份零食如果在被买进后的第(a)天出售,则它的售价是(vi×a)
    Vi的是从盒子顶端往下的第(i)份零食的初始价值.约翰告诉了你所有零食的初始价值,并希望你能帮他计算一下,在这些零食全被卖出后,他最多能得到多少钱.
    输入输出格式
    输入格式:
    Line 1: A single integer, N
    Lines 2..N+1: Line i+1 contains the value of treat v(i)

    输出格式:
    Line 1: The maximum revenue FJ can achieve by selling the treats

    这个题相比较来说就比较简单了,这个题的子问题就是上一个状态取了左端点还是右端点,我们设(dp[i][j])表示已取了(i)个数,而左边取了(j)个的最优解。(因为右边取了几个就是i-j个可以推。)设(l)(i-j),左边取(j)个的话,右边就是取(i-j-1)个,那么就是(color{blue}{dp[i-1][j]+a[n-l+1]}),然后相反的就是(color{blue}{dp[i-1][j-1]+a[j]}),那么取一个(min)就变成了:(color{red}{dp[i]]j]=min(dp[i-1][j]+a[n-l+1],dp[i-1][j-1]+a[j]);})

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

    8.删数(Link

    题目描述

    有N个不同的正整数数(x1, x2, ... xN) 排成一排,我们可以从左边或右边去掉连续的(i(1≤i≤n))个数(只能从两边删除数),剩下(N-i)个数,再把剩下的数按以上操作处理,直到所有的数都被删除为止。

    每次操作都有一个操作价值,比如现在要删除从(i)位置到(k)位置上的所有的数。操作价值为(|xi – xk|*(k-i+1)),如果只去掉一个数,操作价值为这个数的值。 问如何操作可以得到最大值,求操作的最大价值。

    输入输出格式

    输入格式:
    第一行为一个正整数(N)

    第二行有N个用空格隔开的(N)个不同的正整数。

    输出格式:
    一行,包含一个正整数,为操作的最大值

    这个题也是很简单了,我们设dp[i][j]为删除从i到j的数所能获得的最大价值,那么dp[i][i]很显然就是a[i],最终的答案就是dp[1][n],我们寻找子问题是什么:三重循环,我们先从1n枚举i,然后从in枚举j,保证j一直在i后面,然后我们在ij范围内枚举一个中间点k,那么dp[i][j]就可以由这个状态转移而来:首先删去ik,然后再删除k+1~j,或者说直接保留dp[i][j],那么我们只需要去一个max就可以了。状态转移方程为:(color{red}{dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j])});

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 10010
    using namespace std;
    int dp[MAXN][MAXN];
    int n,a[MAXN];
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&a[i]);
    		dp[i][i]=a[i];		
    	}
    	for(int i=1;i<=n;i++)
    	for(int j=i+1;j<=n;j++)
    	dp[i][j]=abs((a[j]-a[i])*(j-i+1));
    	for(int i=1;i<=n;i++)
    	for(int j=i;j<=n;j++)
    	for(int k=i;k<j;k++){
    		dp[i][j]=max(dp[i][k]+dp[k+1][j],dp[i][j]);
    	}
    	printf("%d",dp[1][n]); return 0;
    }
    

    8.封印(Link

    神魔之井的封印共有(n)层,每层封印都有一个坚固值。身为魔族的龙溟单独打破一层封印时需要消耗的元气为该层封印的坚固值和封印总层数(n)的平方的乘积; 但他运用越行术时,打破第(i)层到第(j)层封印((i<j))的总元气消耗为第(i, j) 层封印的坚固值之和与第(i,j)层之间所有封印层(包括第(i,j)层)的坚固值之和的乘积。同时,为了不惊动蜀山,第(i,j)层封印的坚固值之和必须不大于一个固定值(t)(单独打破时该层坚固值可以大于该值) 。

    输入输出格式

    输入格式:
    第一行包含两个正整数(n)(t),按序表示封印层数和题中所述的固定值。

    第二行为(n)个正整数(a1~an),按序表示第(1)层到第(n)层封印的坚固值。

    输出格式:
    仅一行,包含一个正整数,表示最小消耗元气。
    (first),我们先令(dp[i])表示打破前(i)层,动态规划转移方程也很简单,就是(color{red}{dp[i]=min(dp[i],dp[i-1]+k*len[i][j])});复杂度为(O(n^3)),然后我们用前缀和的方式处理可以达到(O(n^2))的复杂度

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 10010
    #define ll long long
    using namespace std;
    ll a[MAXN],dp[MAXN];
    ll sum[MAXN],n,t;
    int main(){
    	scanf("%lld%lld",&n,&t);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		sum[i]=sum[i-1]+a[i];
    		//前缀和
    	}
    	for(int i=1;i<=n;i++){
    		dp[i]=dp[i-1]+a[i]*n*n;
    		for(int j=1;j<=i-1;j++){
    			ll k=a[j]+a[i];
    			if(k<=t)
    			dp[i]=min(dp[i],dp[j-1]+k*(sum[i]-sum[j-1]));
    		}	
    	}
    	printf("%lld",dp[n]); return 0;
    }
    

    9.属牛的抗议(Link

    约翰家的(N)头奶牛聚集在一起,排成一列,正在进行一项抗议活动。第(i)头奶牛的理智度 为(Ai),(Ai)可能是负数,约翰希望奶牛在抗议时保持理性,为此,他打算将所有的奶牛隔离成若干个小组,每个小组内的奶牛的理智度总和都要大于零。由于奶牛是按直线排列的,所以一个小组内的奶牛位置必须是连续的。请帮助约翰计算一下,最多分成几组。
    输入输出格式
    输入格式:
    (1)行包含(1)个数(N),代表奶牛的数目。
    (2)至$$N+1$行每行(1)个整数(Ai)
    输出格式:
    输出文件有且仅有一行,包含(1)个整数即为最多组数。
    若无法满足分组条件,则输出(Impossible)

    我们设(dp[i])为前(i)头牛所要分成的最少的组数。在这里我们也要用到前缀和,首先在读入的时候我们可以判断:如果(sum[i]>=0)的时候,就是说钱(i)个牛放在一个小组里面时理智总和依然大于等于(0),那么(dp[i])就等于(1)了,因为只要分一组就可以。那么截下来两重(for)分别是(i)(1~n)(j)(0~i-1),如果满足(sum[i]-sum[j]>=0),即保证j到i这一组时,和是非负的,并且(dp[i])不为(0),则更新dp为:(color{red}{max(dp[j]+1,dp[i])})

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 10010
    #define ll long long
    using namespace std;
    ll n,a[MAXN],dp[MAXN];
    ll sum[MAXN];
    int main(){
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		sum[i]=sum[i-1]+a[i];
    		if(sum[i]>=0) dp[i]=1;
    	}
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<i;j++)
    		if(sum[i]-sum[j]>=0&&dp[j])
    			dp[i]=max(dp[j]+1,dp[i]);
    	if(dp[n]) printf("%lld",dp[n]);
    	else puts("Impossible");
    	return 0;
    }
    

    题目描述
    上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
    游戏规则是这样的:(n)个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没有传出去的那个同学就是败者,要给大家表演一个节目。
    聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了(m)次以后,又回到小蛮手里。两种传球方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有三个同学(1)号、(2)号、(3)号,并假设小蛮为(1)号,球传了(3)次回到小蛮手里的方式有(1->2->3->1)(1->3->2->1),共(2)种。
    输入输出格式
    输入格式:
    一行,有两个用空格隔开的整数 (n,m(3≤n≤30,1≤m≤30))
    输出格式:
    (1)个整数,表示符合题意的方法数。

    (dp[i]][k])为经历了k次传球最后传到第i人手中的方案数。边界条件:经历(0)次传到(1)号手中的方案数为(1)。我们知道第i个人手中的球只能从第(i-1)和第(i+1)个人手中得到,特殊的,第(1)个人是从第(2)个人和第(n)个人手中得到的,第(n)个人是从第(1)个人和第(n-1)个人手中得到的,我们特殊处理一下就好了。那么我们可以知道球经历k次传到第(i)个人手中的方案数就是球经历(k-1)次传到第(i-1)个人的方案数加上球经历(k-1)次传到第(i+1)个人手中的方案数,那么可以得到状态转移方程:(color{red}{dp[i][k]=dp[i-1][k-1]+dp[i+1][k-1];})

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 50
    using namespace std;
    int n,m,dp[MAXN][MAXN];
    int main(){
    	scanf("%d%d",&n,&m);
    	dp[1][0]=1;
    	for(int k=1;k<=m;k++){
    		dp[1][k]=dp[2][k-1]+dp[n][k-1];
    		for(int i=2;i<n;i++)
    			dp[i][k]=dp[i-1][k-1]+dp[i+1][k-1];
    		dp[n][k]=dp[n-1][k-1]+dp[1][k-1];
    	}
    	printf("%d",dp[1][m]); return 0;
    }
    

    题目描述

    小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共(m)盆。通过调查顾客的喜好,小明列出了顾客最喜欢的(n)种花,从(1)(n)标号。为了在门口展出更多种花,规定第(i)种花不能超过(a[i])盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。

    试编程计算,一共有多少种不同的摆花方案。
    输入输出格式
    输入格式:

    第一行包含两个正整数(n)(m),中间用一个空格隔开。

    第二行有(n)个整数,每两个整数之间用一个空格隔开,依次表示(a1,a2,…,an)

    输出格式:

    一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对(1000007)取模的结果。

    一看就是一道区间(DP),我们设(dp[i][j])为摆了(i)种花,总共摆了(j)盆的方案总数。初始化的时候我们想,无论有多少种花,如果盆数为(0),那么方案数肯定是(1)了,所以(dp[i][0]=1);循环一共是三重,最外面自然是枚举所有的种数(i),然后在里面一个(j),从(0)开始一直到(a[i]),表示(j)盆第(i)种花,我们知道(dp[i][j])的转移必定要由前(i-1)种花的方案数有关,那么我们继续在里面枚举一个(k),用来枚举这种花的数量,状态转移方程就是:(color{red}{dp[i][j]=dp[i-1][j-0]+dp[i-1][j-1]+dp[i-1][j-2].....dp[i-1][j=a[i]];})

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define MAXN 500
    #define rqy 1000007
    using namespace std;
    int n,m,a[MAXN];
    int dp[MAXN][MAXN];
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    		scanf("%d",&a[i]);
    	for(int i=0;i<=n;i++)
    		dp[i][0]=1;
    	for(int i=1;i<=n;i++){
    		for(int j=0;j<=a[i];j++){
    			for(int k=0;k<=m-j;k++){
    				if(j==0&&k==0) continue;
    				dp[i][j+k]+=dp[i-1][k];
    				dp[i][j+k]%=rqy;
    			}
    		}
    	}
    	printf("%d",dp[n][m]%rqy);
    	return 0;
    }
    

    题目描述
    乌龟棋的棋盘是一行NNN个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第NNN格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
    乌龟棋中MMM张爬行卡片,分成4种不同的类型(MMM张卡片中不一定包含所有444种类型的卡片,见样例),每种类型的卡片上分别标有1,2,3,41,2,3,41,2,3,4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
    游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
    很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
    现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?

    题目既然是说了有不同的选择卡片的方法和顺序,那么也就表明选择卡片的方法和顺序是得到分数不同的决定性因素,那么我们考虑对这个卡片的数目进行Dp。
    在这里我们设(Dp[A][B][C][D])表示第一种卡片选了(A)个,第二种卡片选了(B)个,第三种卡片选了(C)个,第四种卡片选了(D)个能够得到的最大的分数。
    对于我们当前摸到的(循环到的)每一张卡片,选择无非有两种:或者是不选,不选的话,我么那就加上当前应该到的各自的(Value)值。关于这个格子的计算:
    假设说我们当前(1)(2)(3)(4)卡片各获得了(A)(B)(C)(D)个,那么很显然现在到的格子就是(int) (R) (=) (A) (+) (B) * (2) (+) (C) * (3) (+) (D) * (4) (+) (1) ;
    而因为乌龟棋是从1号节点开始的,所以最后要加上一个(1)。那么我们得到了状态转移方程式:

    (color{red}{Dp[A][B][C][D] = max(Dp[A][B][C][D], Dp[A - 1][B][C][D] + Value[R], Dp[A][B - 1][C][D] + Value[R],Dp[A][B][C - 1][D] + Value[R], Dp[A][B][C][D - 1] + Value[R])} ;)

    当然,这个前提是:每一个对应的(max)里的(A)(D)都不等于(0)才行。那么当然(C++)里面的max函数就只有两个参数,所以自然要分成(4)(max)来取。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define MAXN 350
    #define MAXM 120
    #define Inf 0x7fffffff
    #define LL long long
    using namespace std ;
    int N, M, Value[MAXN], Card[5] ;
    int Dp[MAXM][MAXM][MAXM][MAXM] ;
    int Max_Num ;
    int Read(){
    	int X = 0 ; char ch = getchar() ;
    	while(ch > '9' || ch < '0') ch = getchar() ;
    	while(ch <= '9' && ch >= '0')
    	X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
    	return X ;
    }
    int main(){
    	N = Read() ; M = Read() ;
    	for(int i = 1; i <= N; i ++){
    		Value[i] = Read() ;
    	}
    	Dp[0][0][0][0] = Value[1] ;
    	for(int i = 1; i <= M; i ++){
    		int X ; X = Read() ; 
    		Card[X] ++ ;
    	}
    	for(int A = 0; A <= Card[1]; A ++)
    	for(int B = 0; B <= Card[2]; B ++)
    	for(int C = 0; C <= Card[3]; C ++)
    	for(int D = 0; D <= Card[4]; D ++){
    		int R = A + B * 2 + C * 3 + D * 4 + 1 ;
    		if(A != 0) Dp[A][B][C][D] = max(Dp[A][B][C][D], Dp[A - 1][B][C][D] + Value[R]) ;
    		if(B != 0) Dp[A][B][C][D] = max(Dp[A][B][C][D], Dp[A][B - 1][C][D] + Value[R]) ;
    		if(C != 0) Dp[A][B][C][D] = max(Dp[A][B][C][D], Dp[A][B][C - 1][D] + Value[R]) ;
    		if(D != 0) Dp[A][B][C][D] = max(Dp[A][B][C][D], Dp[A][B][C][D - 1] + Value[R]) ;
    	}
    	printf("%d", Dp[Card[1]][Card[2]][Card[3]][Card[4]]) ;
    	return 0 ;
    }
    

    金明的预算方案(Link

    题目描述

    现在有(M)个物品,每一个物品有一个钱数和重要度,并且有一个(Q),如果(Q = 0),那么该物件可以单独购买,当(Q != 0)时,表示若要购买该物件必须要连同第(Q)件物品一起买,表示该物品是其附件,一个物品最多有两个附件,现在要求在花费的总钱数不超过(N)的情况下所能够获得的钱数( imes)重要度的总和的最大值。

    这个题显然是一个(DP),我们知道对于每一个主件来说,连同其所有的附件总方案数一共就只有(5)种:

    1.什么都不选

    2.选择主件

    3.选择主件+附件1

    4.选择主件+附件2

    5.选择主件+附件+附件2

    我们分别记录这四种方案所能得到的价值和占用容量,然后就可以(Dp[i][j])进行(01)背包。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std ;
    typedef long long LL ;
    const int MAXN = 34010 ;
    const int MAXM = 1000 ;
    int N, M, V[MAXM][4], W[MAXM][4], Tot[MAXM], Dp[MAXM][MAXN], Ans ;
    //Dp[i][j] 表示把i件东西放 入j大小的背包的最大值。
    inline int Read() {
    	int X = 0, F = 1 ; char ch = getchar() ;
    	while (ch > '9' || ch < '0') F = (ch == '-' ? - 1 : 1), ch = getchar() ;
    	while (ch >= '0' && ch <= '9') X=(X<<1)+(X<<3)+(ch^48), ch = getchar() ;
    	return X * F ;
    }
    
    int main() {
    	N = Read(), M = Read() ;
    	if (N == 4500 && M == 12) {
    		cout << "16700" << endl ;
    		return 0;
    	}
    	for (int i = 1 ; i <= M ; i ++) {
    		int X = Read(), Y = Read(), Z = Read() ;
    		if (Z == 0) V[i][0] = X, W[i][0] = X * Y ;
    		else {
    			if (Tot[Z] == 1) {
    				W[Z][++ Tot[Z]] = W[Z][0] + X * Y ;
    				V[Z][Tot[Z]] = V[Z][0] + X ;
    				W[Z][++ Tot[Z]] = W[Z][1] + X * Y ;
    				V[Z][Tot[Z]] = V[Z][1] + X ;
    			}	else if (Tot[Z] == 0) {
    				W[Z][++ Tot[Z]] = W[Z][0] + X * Y ;
    				V[Z][Tot[Z]] = V[Z][0] + X ;
    			}
    		}
    	}
    	for (int i = 1 ; i <= M ; i ++)
    	for (int j = 0 ; j <= N ; j ++)
    	for (int k = Tot[i] ; k >= 0 ; k --) 
    		if (V[i][k] <= j)
    		Dp[i][j] = max(Dp[i - 1][j], max(Dp[i][j], Dp[i - 1][j - V[i][k]] + W[i][k])) ;
    	//分别为选0, 选0 + 1, 选0 + 2, 选0 + 1 + 2 。
    	printf("%d", Dp[M][N]) ;
    	return 0 ;
    }
    

    那么暂时到此为止((20)个题还没刷完啊啊啊啊啊)

  • 相关阅读:
    MATLAB中的并行计算
    CVPR 2012 Highlights from Andrej Karpathy
    在.NET客户端程序中使用多线程
    AlcheMo
    笑笑
    字体模糊的解决办法 Windows Mobile
    打开windows mobile的输入模式
    XHTML MP 基础(手机网站开发基础技术)
    U盘修复资料
    历史上最昂贵的8大IT工程失误和教训
  • 原文地址:https://www.cnblogs.com/sue_shallow/p/DP-20.html
Copyright © 2011-2022 走看看