rainyroad

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

树形dp:

 题意:有N个职员,编号1~N,每个职员都有一个快乐指数H[ i ]。他们的关系就像是一个以校长为根节点的树,上下级之间构成父子节点关系。现在他们要举办一场宴会,如果直接上级参加了,则直接下属不会参加。所以问你,怎样才能使得宴会上所有人的快乐指数之和最大。

思路:一道树形dp,以树的节点编号为dp的阶段,同时宴会当中某个人的状态只有两种,参加或不参加,所以用dp[ i ][ 0 ]表示 i 号节点不参加,dp[ i ][ 1 ]表示i号节点参加。树形dp,状态的转移,以子节点为前一阶段,父节点的当前阶段由上一阶段子节点推出。所以要先求出叶子节点的dp值(边界值),这就要采用递归的方式求解。状态转移方程就不写了

#include<bits/stdc++.h>
using namespace std;
int n;
int h[6600];
vector<int> tr[6600];
int f[6600][2],v[6600];

void dp(int root)
{

    f[root][0]=0;
    f[root][1]=h[root];
    int len=(int)tr[root].size();
    for(int j=0;j<len;j++)
    {
        int y=tr[root][j];
        dp(y);
        f[root][0]+=max(f[y][0],f[y][1]);
        f[root][1]+=f[y][0];
    }
}


int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&h[i]);
    int x,y;
    for(int i=1;i<=n;i++) {
        cin>>x>>y;
        v[x]=1;
        tr[y].push_back(x);
    }

    int root;
    for(int i=1;i<=n;i++)
    {
        if(!v[i]){root=i;break;}
    }

    dp(root);
    cout<<max(f[root][0],f[root][1])<<"\n";


    return 0;
}

环形dp:

环形dp求解方法有两种:1.先把环形dp当成线性dp求解。然后注意,线性dp变为环形dp后,多了哪些特殊情况,把这些特殊情况处理掉。所以这种方法意思就是,环形dp=线性dp+线性变环形后的多出来的特殊情况。

题意:某个星球上一天有N个小时,0点到1点为第一个小时,1点到2点为第二个小时,以此类推。并且再第 i 个小时睡觉能恢复 Ui 小时的体力。现在又一头牛每天要睡 B 个小时。但这 B 个小时不一定连续,也就是说其休息时间可能是几个小段。并且每一个小段第一个小时不能恢复体力。现在让你给这头安排一个休息计划,使得它每天恢复的体力最多。

思路:先把它按照线性来处理,就是不理会前一天的第N个小时和后一天的第1个小时是连续的。这样由于时间是增长的,选取时间作为dp的阶段,则当前牛只有两个状态要么睡觉,要么醒着。要想要实现最优子结构的状态转移,同时还要知道它已经休息了多长时间。所以用dp[ i ][ j ][ 0 ]表示前 i 小时已经休息了 j 个小时,并且当前是醒着的状态时恢复的最大体力。dp[ i ][ j ][ 1 ]表示当前前 i 个小时休息了 j 个小时,并且当前是睡觉状态恢复的最大体力。 状态转移方程不写了。这个时候dp的边界条件是 dp[ 1 ][ 0 ][ 0 ]=dp[ 1 ][ 1 ][ 1 ]=0; 而相比于环形,环形仅仅多了一种极端情况 dp[ 1 ][ 1 ][ 1 ]=U[ 1 ],所以环形情况下只要令初始dp值为它,再跑一次线性dp就可以了。

#include<iostream>
#include<stdio.h>
#include<cmath>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f;
typedef long long ll;
ll U[4000];
int N,B;
ll ans1,ans2;
ll dp[4000][2];

/*struct node
{
    int id;
    int state;
}D[4000][4000][2],D2[4000][4000][2];

void update1(int cur,int i,int j)
{
    if(dp[i-1][j][0]>dp[i-1][j][1])
        {
            dp[i][j][0]=dp[i-1][j][0];
            if(cur==1)
            {
            D[i][j][0].id=i-1;D[i][j][0].state=0;
            }
            else
            {
            D2[i][j][0].id=i-1;D2[i][j][0].state=0;
            }
        }
        else
        {
            dp[i][j][1]=dp[i-1][j][1];
            if(cur==1)
            {
            D[i][j][0].id=i-1;D[i][j][0].state=1;
            }
            else
            {
                D2[i][j][0].id=i-1;D2[i][j][0].state=1;
            }
        }
}

void update2(int cur,int i,int j)
{
    //dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+U[i]);
    if(dp[i-1][j-1][0]>dp[i-1][j-1][1]+U[i])
    {
        dp[i][j][1]=dp[i-1][j-1][0];
        if(cur==1)
        {
        D[i][j][1].id=i-1;
        D[i][j][1].state=0;
        }
        else
        {
          D2[i][j][1].id=i-1;
          D2[i][j][1].state=0;
        }
    }
    else
    {
        dp[i][j][1]=dp[i-1][j-1][1]+U[i];
        if(cur==1)
        {
        D[i][j][1].id=i-1;
        D[i][j][1].state=1;
        }
        else
        {
          D2[i][j][1].id=i-1;
          D2[i][j][1].state=1;
        }
    }

}
*/

int main()
{
    cin>>N>>B;
    for(int i=1;i<=N;i++)
    {
        cin>>U[i];
    }

    memset(dp,-inf,sizeof dp);
    dp[0][0]=0;
    dp[1][1]=0;

    for(int i=2;i<=N;i++)
     for(int j=i;j>=0;j--)
    {
        dp[j][0]=max(dp[j][0],dp[j][1]);
        if(j-1>=0)
        dp[j][1]=max(dp[j-1][0],dp[j-1][1]+U[i]);
    }

    ans1=max(dp[B][0],dp[B][1]);
    memset(dp,-inf,sizeof dp);

    dp[1][1]=U[1];
    for(int i=2;i<=N;i++)
     for(int j=i;j>=0;j--)
    {
        dp[j][0]=max(dp[j][0],dp[j][1]);
        if(j-1>=0)
        dp[j][1]=max(dp[j-1][0],dp[j-1][1]+U[i]);
    }

    ans2=dp[B][1];

    cout<<max(ans1,ans2)<<"\n";

    return 0;
}

第二种处理方法:复制一遍要处理的序列加到第一条序列末尾。

状态 压缩dp:有的时候不仅需要由上一阶段的最优值来推下一阶段的最优值,并且下一阶段的某些状态与上一阶段的某些状态有依赖关系,所以这个时候我们不仅要保存上一阶段的最优值,而且还需要上一阶段的状态。这个时候我们把状态压缩成一个十进制的整数,十进制转化为二进制其 0 1的分布情况就代表了状态分布。

题意:给你一个N*M的棋盘,现在让你把它分割成若干个1*2的长方形。有多少种分割方案。

思路:行再线性递增,选取行作为dp的阶段,其中的每一个小格子只有两种情况要么是属于横着放的1*2的长方形,要么是属于竖着放的1*2的小格子,当它属于竖着放的1*2的小格子的时候。它对后面一行就会产生影响,后一行必定有一个格子要接在它的后面。而一行刚好有M个格子,我们就可以用一个M位的二进制的每一位是0还是1来表示当前格子是竖着的还是横着的。则 状态定义 为 dp[ i ][ j ]表示第  i  行,当前格子分布情况为  j  ;

 

最短路:

题意:当前有M个任务,每个任务给定开始时间,和结束时间。小明按照一定的规则来做任务,即当前时刻若小明空闲,并且有任务开始,则分配给小明。若当前有多个任务,则选择其中一个任务。问怎样安排任务给小明,则小明的空闲时间最多。

思路:原本是dp,但是dp不会,题解也看不懂。把每个任务的开始时刻作为父节点,其任务结束时刻作为子节点,把之间相隔的时间作为边权值,但是这样的话都得到的图是很多个分裂开来的树。则把出度为0的节点即,任务结束的时刻与它的下一时刻的时间节点相连,但由于中间没有任务,所以把边权值设为0,这样就得到了一个带权的有环图,再环上跑一边dijkstra,得到最短路,最后再用总时间减去最短路就得到最多休息时间了。

但是这道题一开始把优先队列,写成一般队列了,还一直没找出错。。。

#include<bits/stdc++.h>
using namespace std;
const int maxn=20000;
priority_queue< pair<int,int> > q;

int n,m;
int cnt=0;

int head[maxn];
struct Edge{
  int next;
  int to;
  int w;
}edge[maxn];

void add(int u,int v,int w)
{
    edge[++cnt].next=head[u];
    edge[cnt].to=v;
    edge[cnt].w=w;
    head[u]=cnt;
}

int dis[maxn];
int vis[maxn];
void dijkstra()
{
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    q.push(make_pair(0,1));
    //q.push(1);
    int now;
    while(!q.empty())
    {
        now=q.top().second;
        q.pop();
        if(vis[now]) continue;
        //vis[now]=false;
        vis[now]=1;
        for(int i=head[now];i;i=edge[i].next)
        {
            if(dis[edge[i].to]>dis[now]+edge[i].w)
            {
                dis[edge[i].to]=dis[now]+edge[i].w;

                q.push(make_pair(-dis[edge[i].to],edge[i].to));
                //vis[edge[i].to]=1;
            }
        }

    }
}


int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++)
    {
       cin>>x>>y;
       add(x,x+y,y);
    }

    for(int i=1;i<=n;i++)
    {
        if(!head[i])
        {
            add(i,i+1,0);
        }
    }

    dijkstra();
    cout<<n-dis[n+1];

    return 0;
}

 

dpP1140 相似基因

题意:给你一个两个DNA序列,问他们的最大相似度是多少。

思路:这道题绕的很,首先由两个序列的最长上升子序列,可以想到 维度肯定是两个 。并且 dp[ i ][ j ]表示第一个DNA序列前 i 个与 第二个DNA 序列前 j 个的最大相似度。并且其肯定是由 dp[ i-1 ][ j ] 和dp[ i-1 ][ j-1 ],dp[ i-1 ][ j-1 ]推出来的。可是这个题把空白碱基对加上,让人迷糊的很。所以举个例子  dp[ 4 ][ 3 ],若其由 dp[ 4 ][ 2 ]推出,则现在第二个DNA序列其第3个基因只能和第一个DNA序列的空白基因相配,,,若其由dp[ 3 ][ 3 ]推出,则其第一个DNA序列的第四个基因只能与第二个DNA序列的空白基因相配,若由dp[ 3 ][ 2] 推出,其第一个DAN序列第四个基因和第二个DNA序列的第3个基因直接相配。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;

int dp[150][150];
int x,y;
string s1,s2;
int char_num(char r)
{
    if(r=='A') return 1;
    if(r=='C') return 2;
    if(r=='G') return 3;
    if(r=='T') return 4;
}

int main()
{
     int  v[6][6]={
       {0,0,0,0,0,0},
       {0,5,-1,-2,-1,-3},
       {0,-1,5,-3,-2,-4},
       {0,-2,-3,5,-2,-2},
       {0,-1,-2,-2,5,-1},
       {0,-3,-4,-2,-1,0}
    };

    cin>>x>>s1;
    cin>>y>>s2;

    memset(dp,-inf,sizeof dp);

    dp[0][0]=0;
    for(int i=1;i<=x;i++)
    {
    //cout<<"---"<<s1[i-1]<<"\n";
   // cout<<"+++"<<char_num(s1[i-1])<<"\n";
    dp[i][0]=dp[i-1][0]+v[char_num(s1[i-1])][5];
    }
   for(int i=1;i<=y;i++)
    dp[0][i]=dp[0][i-1]+v[5][char_num(s2[i-1])];

    for(int i=1;i<=x;i++)
     for(int j=1;j<=y;j++)
    {
        dp[i][j]=max(dp[i][j],dp[i][j-1]+v[5][char_num(s2[j-1])]);
        dp[i][j]=max(dp[i][j],dp[i-1][j]+v[char_num(s1[i-1])][5]);
        dp[i][j]=max(dp[i][j],dp[i-1][j-1]+v[char_num(s1[i-1])][char_num(s2[j-1])]);
    }

    cout<<dp[x][y];
    return 0;
}

 

先贪心在dp的题

有的题目(一般会给出n个物品), 当前这个物品对最优值的贡献与他和其他物品的相对顺序有关,也就是说它的贡献度不是一个常量。这个时候一般就要把这些物品先按优先级排一个序。为什么普通的背包问题不用排序,因为每个物品对最优值的贡献度是唯一的,因为它们的价值是唯一的。

那么这种优先级该如何确定啦,以下面两道题为例子,其实方法都一样,一开始只考虑相邻两项

1.

题意:现有M个饼干,分给N个孩子。每个孩子有一个贪婪度,g[ i ]。对于每个孩子,如果有a[ i ]个孩子比他拿到的饼干多,那么他就会产生a[ i ]*b[ i ]的怨气。现在问你如何安排才能使得怨气总和最小。

题意:每个孩子对总怨气的贡献与他和别的孩子的相对顺序有关,所以先确定优先级,考虑相邻孩子a和孩子b,且怨气:

a<b.则如果孩子a排在前面其对总的怨气贡献度为 0*a+1* b=b。孩子b排在前面其对总的怨气的贡献度为 0*b+1*a=a。很明显

a<b.所以应当把怨气大的排在前面。这样优先度,就确定了。

2.题意:从0开始计时,一直到T时刻结束。现在给你n种食材,每种食材都有三个属性,ai,bi,ci,  ci为用这种食材做饭所要花的时间。

每种食材对美味度的贡献度为ai-t*bi。问你在T时刻结束时,总的美味度最大可能是多少。

思路:每种食材对总美味度的贡献依旧不是一个常数,这与他和别的食材的相对顺序有关,所以先考虑相邻两项.先做a[ 1 ]后做 a[ 2 ]对美味度的贡献度,先做a[ 2 ]在做 a[ 1 ]对美味度的贡献度。

a[ 1 ]-(p+c[ 1 ])*b[ 1 ]+a[ 2 ]-(p+c[ 1 ]+c[ 2 ])b[ 2 ]  与a[ 2 ]-(p+c[ 2 ])*b[ 2 ]+a[ 1 ]-(p+c[ 1 ]+c[ 2])*b[ 1 ]进行大小比较,就能得出一式大于2式子的条件是c[1]*b[2]<c[2]*b[1].这样就把乘积小的排在前面。

剩下的就01背包一下

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int T,n;
ll dp[110000];

struct node{
  ll a,b,c=0;
}dot[100];

bool cmp(node x,node y)
{
    return x.c*y.b<y.c*x.b;
}


int main()
{
    scanf("%d%d",&T,&n);
    for(int i=1;i<=n;i++)
    scanf("%d",&dot[i].a);
    for(int i=1;i<=n;i++)
    scanf("%d",&dot[i].b);
    for(int i=1;i<=n;i++)
    scanf("%d",&dot[i].c);

    sort(dot+1,dot+1+n,cmp);

    for(int i=1;i<=n;i++)
    {
     for(int j=T;j>=dot[i].c;j--)
      dp[j]=max(dp[j],dp[j-dot[i].c]+dot[i].a-dot[i].b*j);
    }

    ll ans=0;
    for(int i=0;i<=T;i++)
     ans=max(ans,dp[i]);

    cout<<ans;

    return 0;
}

 

posted on 2019-07-20 00:59  rainyroad  阅读(524)  评论(0编辑  收藏  举报