2015/7/20

暑期学习7.20

按计划是学习红书9.1,9.2的内容。留点记录。

  • 9.1数字三角形
  • 9.2DAG上的动态规划

9.1数字三角形

前言
数字三角形也不是第一次接触,这里也不在赘述。而书上这个例子也是为了引出:动态规划的核心是状态和状态转移方程。书上给了三种方法,第一种是个反例,不多说,说一下方法二递推计算以及方法三记忆化搜索。
1.递推计算
个人理解,相较于方法一的函数递归,方法二更体现了状态的保存,让计算的次数减小了许多,这是我能体会到的。说一些细节上的东西吧。
1.在操作上,例如数组的开始可以从1开始,这样为边界处理带来了方便。
2.抄一下书上的内容,“递推的关键是边界和计算顺序。在多数情况下,递推法的时间复杂度是:状态总数X每个状态的决策个数X决策时间。如果不同状态的决策个数不同,需具体问题,具体分析。
3.就这道题来说,递推还是很容易的,但如果换了个情况我还行吗?总之,我先记住,清楚计算顺序以及边界。

    /*
        memset(dp,0,sizeof(dp));
        for(int i=1; i<=n; i++)
        {
            dp[n][i]=a[n][i];
        }

        for(int i=n-1; i>0; i--)
        {
            for(int j=1; j<=i; j++)
            {
                dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
            }
        }
        cout<<dp[1][1]<<endl;//注意从一开始留出边界  注意i+1
        */

2.记忆优化搜索
1.记忆优化搜索,程序虽然是递归的,但是关键在于,它把计算结果保存在数组d中,根据题目的不同,可以用一些条件判断出该状态是否还需要计算,减少了许多重复的问题。
2.用一下书上的话“它虽然不像递推法那样显示地指明了计算顺序,但仍然可以保证每个结点只能访问一次”。既可以用一些东西作为记忆,避免重复计算。“采用记忆优化搜索时,不必事先确定各状态的计算顺序,但需要记录每个状态是否已经计算过。”

int solve(int i,int j)
{
    if(dp[i][j]>=0)
        return dp[i][j];
    return dp[i][j]=a[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
}
。。。
memset(dp,-1,sizeof(dp));
cout<<solve(1,1)<<endl;

总结
总的来说这一节是引出了两个典型的方法,前者递推注意边界以及计算顺序,后者注意如何判断已经计算过然后再递归。

9.2DAG上的动态规划

前言
DAG即Directed Acyclic Graph,是有向无环图的意思,很多问题都可以转化为DAG上的最长路,最短路,或路径计数问题。希望我能看出来:)
1.最长路及其字典序
这一章节主要讲的就是“嵌套矩形”的问题,概括起来,它是“不固定起点的最长路径”,d(i)=max{d(j)+1|(i,j)属于E},我们可以采用记忆优化搜索。其中有一个技巧,对于d[i],我们可以声明它的一个引用ans来进行操作,会方便许多。这个关键是输出字典序让我头疼了不少,我们可以选择最大d[i]所对应的i,如果i有多个,则选择最小的,接下来选择d(i)=d(j)+1合情合理。而如果要打印全部,那么必须要用一个数组记录,而不是简单的去掉break。

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>

using namespace std;

int n;
int x[1010],y[1010],d[1010];
vector<int>ma[1005];
int a[1010];
int max1=0;
int maxnum;


int dp(int i)
{
    int &ans=d[i];
    if(ans>0)
        return ans;
    ans=1;
    for(int j=0; j<ma[i].size(); j++)
    {
        ans=max(ans,dp(ma[i][j])+1);
    }
    return ans;
}

void print_all(int p)
{
    if(p==0)
    {
        for(int i=max1; i>0; i--)
            printf("%d ",a[i]);
        printf("\n");
        return;
    }

    for(int i=0; i<n; i++)
    {
        if(p==d[i])
        {
            a[p]=i;
            print_all(p-1);
        }
    }
}


void print_ans(int i)
{
    printf("%d ",i);
    for(int j=0; j<ma[i].size(); j++)
        if(d[i]==d[ma[i][j]]+1)
        {
            print_ans(ma[i][j]);
            break;
        }
}



int main()
{
    int kase;
    scanf("%d",&kase);
    while(kase--)
    {
        scanf("%d",&n);
        for(int i=0; i<n; i++)
            scanf("%d%d",&x[i],&y[i]);
        memset(d,0,sizeof(d));

        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++)
            {
                if((x[i]<x[j]&&y[i]<y[j])||(x[i]<y[j]&&y[i]<x[j]))
                    ma[i].push_back(j);
            }


        for(int i=0; i<n; i++)
            max1=max(max1,dp(i));

        for(int i=0; i<n; i++)
        {
            if(d[i]==max1)
                maxnum=i;
            break;
        }




        printf("****%d\n",max1);

        print_all(max1);
        //print_ans(maxnum);
        for(int i=0; i<n; i++)
        {
            if(ma[i].size()!=0)
                ma[i].clear();
        }
    }
    return 0;
}

2.固定终点的最长路和最短路
这部分主要讲的是硬币问题。d(i)的确切含义变为“从结点i出发到结点0的最长路径长度”。要注意的是,s可以为0,所以用-1来表示没有算过。还需要注意的是,有可能d[s]无法到达0。如果用特殊值表示还没算过,则必须和其他特殊值区分开。当然啦,我们还可以用vis[s]来记录有没有算过。

minv[0]=maxv[0]=0;
    for(int i=1; i<=S; i++)
    {
        minv[i]=INF;
        maxv[i]=-INF;
    }
    for(int i=1; i<=S; i++)
        for(int j=1; j<=n; j++)
            if(i>=V[j])
            {
                minv[i]=min(minv[i],minv[i-V[j]]+1);
                maxv[i]=max(maxv[i],maxv[i-V[j]]+1);
            }

就这样吧,感觉这么搞效率挺低的,以后改一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值