动态规划--简单递推

动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间效率高,代码量少,多元性强,主要考察思维能力、建模抽象能力、灵活度。

*************************************************************************************************************

动态规划 英语Dynamic programming ,DP) 是一种在 数学 计算机科学 经济学 中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有 重叠子问题最优子结构 性质的问题,动态规划方法所耗时间往往远少于朴素解法。

动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其 记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈 指数增长时特别有用。

动态规划问题满足三大重要性质

最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

无后效性将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
     
解决问题的一般步骤
(1)建立模型,确认状态
(2)找出状态转移方程
(3)找出初始条件

**********************************************************************************************************

递推:
递推形式比较单一,从前往后,分类枚举。

1. hdu 2048 树塔   简单从前往后推
2. hdu 2018 母牛的故事   简单递推计数
3. hdu 2041 超级楼梯   Fibonacci
4. hdu 2050 折线分割平面   找递推公式
5. zoj 3747 Attack on Titans  带限制条件的计数递推

**********************************************************************************************************
1. hdu 2048 树塔   简单从前往后推

转移方程:  dp[i][j]=max(dp[i-1][j]+a[i][j],dp[i-1][j-1]+a[i][j]);

#include<iostream>
#include<cmath>
using namespace std;
int a[110][110],dp[110][110];
int main()
{
    int i,j;
    int t,n;
    cin>>t;
    while(t--)
    {
        cin>>n;
        for(i=0;i<n;i++)
        {
            for(j=0;j<=i;j++) cin>>a[i][j];
        }
        for(i=0;i<n;i++)
        {
            for(j=0;j<=i;j++)
            {
                if(j==0) dp[i][j]=dp[i-1][j]+a[i][j];
                else if(j==i) dp[i][j]=dp[i-1][j-1]+a[i][j];
                else dp[i][j]=max(dp[i-1][j]+a[i][j],dp[i-1][j-1]+a[i][j]);
            }
        }
        int maxx=dp[n-1][0];
        for(j=0;j<n;j++) maxx=max(maxx,dp[n-1][j]);
        cout<<maxx<<endl;

    }
    return 0;
}


2. hdu 2018 母牛的故事   简单递推计数
有一头母牛,它每年年初生一头小母牛。每头小母牛从第四个年头开始,每年年初也生一头小母牛。请编程实现在第n年的时候,共有多少头母牛?

转移方程: f(n) = n   n<=4
                  f(n)= f(n-1) + f(n-3)  n>4

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[60],n;
int cow(int n)
{
    if(a[n]<=4)
        return a[n];
    else
    {
        a[n]=cow(n-1)+cow(n-3);
        return a[n];
    }
}
int main()
{
   memset(a,0,sizeof(a));
   for(int i=1;i<=4;i++) a[i]=i;
   while(scanf("%d",&n)&&n)
  {
      int num=cow(n);
      printf("%d\n",num);
  }
  return 0;
}

3. hdu 2041 超级楼梯   Fibonacci
有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?

转移方程:f(n)=f(n-1)+f(n-2)   n>3
                 f(1)=1   f(2)=2 

#include <stdio.h>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
    int i,n,x;
    int  a[50];
    while(scanf("%d",&n)!=EOF)
    {
        while(n--)
        {
            scanf("%d",&x);
            a[1]=1;
            a[2]=2;
            for(i=3; i<=x-1; i++)
                a[i]=a[i-1]+a[i-2];

            printf("%d\n",a[x-1]);
        }
    }
    return 0;
}


4. hdu 2050 折线分割平面   找递推公式

扫描二维码关注公众号,回复: 2258040 查看本文章

(1) n条直线最多分平面问题

题目大致如:n条直线,最多可以把平面分为多少个区域。
析:可能你以前就见过这题目,这充其量是一道初中的思考题。但一个类型的题目还是从简单的入手,才容易发现规律。当有n-1条直线时,平面最多被分成了f(n-1)个区域。则第n条直线要是切成的区域数最多,就必须与每条直线相交且不能有同一交点。这样就会得到n-1个交点。这些交点将第n条直线分为2条射线和n-2条线断。而每条射线和线断将以有的区域一分为二。这样就多出了2+(n-2)个区域。
    
     故:f(n)=f(n-1)+n
                 =f(n-2)+(n-1)+n
                 ……
                 =f(1)+1+2+……+n
                 =n(n+1)/2+1

(2) 折线分平面(hdu2050)
根据直线分平面可知,由交点决定了射线和线段的条数,进而决定了新增的区域数。当n-1条折线时,区域数为f(n-1)。为了使增加的区域最多,则折线的两边的线段要和n-1条折线的边,即2*(n-1)条线段相交。那么新增的线段数为4*(n-1),射线数为2。 由于是折线,在头上的2条线段会形成一个三角形,而少增加一个平面部分,所以第n条折线带来的新增的平面部分为4(n-1)+2-1
       
       故:f(n)=f(n-1)+4(n-1)+2-1
                   =f(n-1)+4(n-1)+1
                   =f(n-2)+4(n-2)+4(n-1)+2
                   ……
                   =f(1)+4+4*2+……+4(n-1)+(n-1)   
                   =2n^2-n+1

(3) 封闭曲线分平面问题
题目大致如设有n条封闭曲线画在平面上,而任何两条封闭曲线恰好相交于两点,且任何三条封闭曲线不相交于同一点,问这些封闭曲线把平面分割成的区域个数。
析:当n-1个圆时,区域数为f(n-1).那么第n个圆就必须与前n-1个圆相交,则第n个圆被分为2(n-1)段线段,增加了2(n-1)个区域。
             故: f(n)=f(n-1)+2(n-1)     
                          =f(1)+2+4+……+2(n-1)
                          =n^2-n+2

(4)平面分割空间问题(hdu1290)
由二维的分割问题可知,平面分割与线之间的交点有关,即交点决定射线和线段的条数,从而决定新增的区域数。试想在三维中则是否与平面的交线有关呢?当有n-1个平面时,分割的空间数为f(n-1)。要有最多的空间数,则第n个平面需与前n-1个平面相交,且不能有共同的交线。即最多有n-1 条交线。而这n-1条交线把第n个平面最多分割成g(n-1)个区域。(g(n)为(1)中的直线分平面的个数)此平面将原有的空间一分为二,则最多增加g(n-1)个空间。
         
        故:f=f(n-1)+g(n-1)    ps:g(n)=n(n+1)/2+1
                =f(n-2)+g(n-2)+g(n-1)
                ……
                =f(1)+g(1)+g(2)+……+g(n-1)
                =2+(1*2+2*3+3*4+……+(n-1)n)/2+(n-1)
                =(1+2^2+3^2+4^2+……+n^2-1-2-3-……-n )/2+n+1
                =(n^3+5n)/6+1

#include<stdio.h>
int main()
{
    int c, n;
    __int64 item[10001] = {0,2,7};
    for(int i=3;i<10001;i++){
        item[i] = item[i-1]+4*(i-1)+1;
    }
    scanf("%d", &c);
    while(c--){
        scanf("%d", &n);
        printf("%I64d\n", item[n]);        
    }
}

5. zoj 3747 Attack on Titans  带限制条件的计数递推

题意:有三个兵团 GRP,现在需要组成N人团队,至少调动M个连续G士兵,至多调动K个连续R士兵,问有多少种组合情况


思路:由于又是至多又是至少没办法处理,所以统一转换成至多,对于事件A={至多N个G士兵连续},事件B={至多m-1个G士兵连续},那么A-B={连续人数在m到n中取值,即至少m个连续士兵}。统一之后,设定一个二维数组,dp[i][3]: dp[i][0]表示对于第i个位置,放G士兵,同理,dp[i][1],dp[i][2]分别表示第i个位置放R,和P。

        最大连续u个G,连续v个R;

        对于当前第i个位置,sum=dp[i-1][0]+dp[i-1][1]+dp[i-1][2];

        由于放P对整个没有影响,所以,dp[i][2]=sum;

        对于A事件  
                当i <= u 时,dp[i][0]=sum;                         //至多连续u个,那么小于等于u,就可以直接放G
                当i==u+1时,dp[i][0]=sum-1;                 // 对于现在这个位置,之前可能出现连续u个的情况,当前要放G,就要                   减去那一种情况
                当i>u+1时,dp[i][0]=sum-dp[i-u-1][1]-dp[i-u-1][2];      //此时要减去i前面已经出现连续u个G的情况,即从i-u到                   i-1这一段都是G, 我们可以减去不符合约束的方案数,当 i - 1 到 i - u都是G士兵时 有多少种方案?肯定当i - u -1是R                     或者P啦          
         对于B事件                 //对于B事件同理
                当i<=v时,dp[i][1]=sum;
                当i==v+1时,dp[i][1]=sum-1;
                当i>v+1时,dp[i][2]=sum-dp[i-v-1][0]-dp[i-v-1][2];

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define mod 1000000007
long long dp[1000010][3];  //G R P
long long n, m, k;

int fun(int u, int v)
{
	dp[0][2] = 1;   //初始化,对于第零个位置,整体为1。也可以理解为第0个位置为p,并没有什么影响
	dp[0][0] = dp[0][1] = 0;
	long long sum = 0;

	for (int i = 1; i <= n; i++)
	{
		sum = ((dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2]) % mod + mod) % mod;

		dp[i][2] = sum;

		if (i <= u) dp[i][0] = sum;
		if (i == u + 1) dp[i][0] = ((sum - 1) % mod + mod) % mod;
		if (i>u + 1)  dp[i][0] = ((sum - dp[i - u - 1][1] - dp[i - u - 1][2]) % mod + mod) % mod;

		if (i <= v) dp[i][1] = sum;
		if (i == v + 1) dp[i][1] = ((sum - 1) % mod + mod) % mod;
		if (i>v + 1)  dp[i][1] = ((sum - dp[i - v - 1][2] - dp[i - v - 1][0]) % mod + mod) % mod;

	}
	return (dp[n][0] + dp[n][1] + dp[n][2]) % mod;
}

int main()
{

	while (~scanf("%lld%lld%lld", &n, &m, &k))
		printf("%lld\n", ((fun(n, k) - fun(m - 1, k)) % mod + mod) % mod);

	return 0;
}


End 

猜你喜欢

转载自blog.csdn.net/qq_34777600/article/details/79675573