进阶动态规划

(好几万年前的草稿箱里的存货了…还是先放出来吧…反正近期是不更了hhh)

5.数位统计dp

例题:计数问题

在这里插入图片描述

输入
1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0
输出
1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247
思路

如果暴力硬算的话,108*8会超时。

数位dp尤其要注意分情况讨论,转换成类似前缀的问题。

count(n,x) 1~n中x出现的次数,则上述问题可转换为count(b,x)-count(a-1,x)

分别求出x在每一位上出现的次数,再加起来即可。


6.状态压缩dp

数据范围比较小的时候,一般20以内,考虑状态压缩。
例题1:摆放小方块

在这里插入图片描述

输入
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出
1
0
1
2
3
5
144
51205
思路

可以化简为求横向长方形摆放的方案数,因为横向的确定之后,纵向的就只能依次摆放好了。
dp[i][j] 表示摆放第i列,i-1列伸出来横着的方格状态为j的方案数目

在这里插入图片描述
时间复杂度分析:状态数 11*211,状态转移需要枚举211次,所以总计算量大概 4 *107

#include <iostream>
#include <cstring>
#include <vector>
const int maxn=15;
using namespace std;
long long dp[maxn][1<<maxn];
//f[i][j]表示摆放第i列,i-1列伸出来横着的方格状态为j的方案数
bool st[1<<maxn];
int main()
{
    int n,m;
    while(cin>>n>>m&&n&&m)
    {
        //预处理出行连续相邻0个数为偶数个的合法情况
        for(int i=0;i<1<<n;i++)
        {
            int cnt=0;
            st[i]=true;
            for(int j=0;j<n;j++)//遍历这个数的每一位
            {
                if(i>>j &1)
                {
                    if(cnt&1)
                        st[i]=false;
                    cnt=0;
                }else
                    cnt++;
            }
            if(cnt&1)
            st[i]=false;
        }

        memset(dp,0,sizeof(dp));//对每一组要重新初始化dp
        dp[0][0]=1;//第0~1列的摆放方案,显然为0,但是0也是一种解,所以dp[0][0]=1
        for(int i=1;i<=m;i++)//枚举每一列
            for(int j=0;j<1<<n;j++)//枚举第i列的状态j
                for(int k=0;k<1<<n;k++)//枚举第i-1列的状态k
                    if((j&k)==0&&st[j|k])
                        dp[i][j]+=dp[i-1][k];
                              //最后一列是m-1列,因为是从0开始的
        cout<<dp[m][0]<<endl;//求的是第m列排好,并且第m列不向后伸出块的情况
    }
    return 0;
}
例题2:最短Hamilton路径

在这里插入图片描述

输入
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出
18
思路

如果暴力做的话,相当于对所有点做全排列(n!),找最优路径(n),时间复杂度为O(n!*n),不可取。

这里我们依然用状态dp,用一个整数来表示一个状态。

f[i][j] 表示所有“经过的点集是i,最后位于点j的路线”的长度的最小值。
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <vector>
const int maxn=20;//注意这里开不了太大,因为下面有个移位
using namespace std;
int dp[1<<maxn][maxn],a[maxn][maxn];
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            cin>>a[i][j];
    memset(dp,0x3f,sizeof(dp));
    dp[1][0]=0;//注意边界条件的初始化

    for(int i=0;i<1<<n;i++)
        for(int j=0;j<n;j++)
            if(i>>j &1)//遍历经过的点
            {
                for(int k=0;k<n;k++)//确定谁是倒数第二个点
                    if(i>>k &1)//这里的判断也可以(i-1<<j)>>k 排除相同的情况
                        dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+a[k][j]);
            }
    cout<<dp[(1<<n)-1][n-1]<<endl;
    return 0;
}

if((i-(1<<j))>>k &1)可以直接改成if(i>>k &1)只判断k在不在路径中,只有j==k时才有影响,但此时f[i-(1<<j)][k]这个状态不合法,是正无穷,所以它不会对其他状态产生任何影响。

7.树形dp

例题:

在这里插入图片描述

输入
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出
5
思路

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <vector>
const int maxn=6010;
using namespace std;
int dp[maxn][2];
int n;
int h[maxn],e[maxn],nex[maxn],idx;
int happy[maxn];
bool ownf[maxn];
void add(int a,int b)
{
    e[idx]=b;
    nex[idx]=h[a];
    h[a]=idx++;
}
void dfs(int u)
{
    dp[u][1]=happy[u];
    for(int i=h[u];i!=-1;i=nex[i])
    {
        int j=e[i];
        dfs(j);

        dp[u][1]+=dp[j][0];
        dp[u][0]+=max(dp[j][0],dp[j][1]);
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>happy[i];
    memset(h,-1,sizeof(h));
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        cin>>a>>b;//职员 上司 
        add(b,a);//b是a的父结点,a是到达点
        ownf[a]=true;
    }
    //要自己求出根结点
    int root=1;
    while(ownf[root])
        root++;//没有父结点的点是根结点
    dfs(root);
    cout<<max(dp[root][0],dp[root][1]);
    return 0;
}

happy值可能是负数,不过没关系,在以u为根的子树中,当一个点都不选的时候,总和是0,所以f[u][0]>=0,所以max(f[u][0],f[u][1])>=0

8.记忆化搜索

以递归的方式写动态规划

在这里插入图片描述在这里插入图片描述

输入
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
输出
25
思路

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <vector>
const int maxn=6010;
using namespace std;
int h[maxn][maxn];
int dp[maxn][maxn];
int n,m;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int dfs(int x,int y)
{
    if(dp[x][y]!=0)
        return dp[x][y];

    dp[x][y]=1;
    for(int i=0;i<4;i++)
    {
        int nx=x+dx[i],ny=y+dy[i];
        if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&h[x][y]>h[nx][ny])
            dp[x][y]=max(dp[x][y],dfs(nx,ny)+1);
            //不能写成dp[nx][ny]+1,dp[nx][ny]可能还没有算出来,你没有递归
    }
    return dp[x][y];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>h[i][j];

    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            dfs(i,j);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)//遍历起始点,看哪个起始点出发能最大
            ans=max(ans,dp[i][j]);
    cout<<ans<<endl;
    return 0;
}
如果直接dfs,会遍历所有的情况,有很多重复计算,会超时。
#include <iostream>
#include <cstring>
#include <vector>
const int maxn=6010;
using namespace std;
int h[maxn][maxn];
int dp[maxn][maxn];
int n,m;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int maxlen=1;
void dfs(int x,int y,int k)
{
    if(k>maxlen)
        maxlen=k;

    for(int i=0;i<4;i++)
    {
        int nx=x+dx[i],ny=y+dy[i];
        if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&h[x][y]>h[nx][ny])
            dfs(nx,ny,k+1);//会遍历所有情况
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>h[i][j];

    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            dfs(i,j,1);
    
    cout<<maxlen<<endl;
    return 0;
}
优先队列+bfs (转载)
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
struct node{
    int i,j,num;
};
struct cmp1{
    bool operator()(node x,node y){
        return x.num>y.num;
    }
};
priority_queue<node,vector<node>,cmp1>q;

int n,m,f[305][305],g[305][305],maxn;

void bfs() {
    while(!q.empty()){
        node tmp = q.top(); 
        int i = tmp.i;
        int j = tmp.j;
        int now = tmp.num;
        q.pop();

        if(g[i-1][j]<now) f[i][j]=max(f[i][j],f[i-1][j]+1);
        if(g[i+1][j]<now) f[i][j]=max(f[i][j],f[i+1][j]+1);
        if(g[i][j-1]<now) f[i][j]=max(f[i][j],f[i][j-1]+1);
        if(g[i][j+1]<now) f[i][j]=max(f[i][j],f[i][j+1]+1);
        if(maxn<f[i][j]) maxn=f[i][j];
    }
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j]=1;
            scanf("%d",&g[i][j]);
            node a;
            a.i=i;
            a.j=j;
            a.num=g[i][j];
            q.push(a);
    }
    bfs();
    printf("%d\n",maxn);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值