DP——板子

DP

最长上升子序列(LIS)——O(nlogn

定义
最长上升子序列是:
1:只包含ai的子序列
2:满足j<i并且aj<ai
3:子序列长度最长
朴素算法O(n^2) dp+二分(nlogn)
在这里插入图片描述
在这里插入图片描述

dp数组维护的是以pos位置结尾最小可能的值,如果要打印最长上升子序列路径,开辟新数组path,倒着找合法序列,正序寻找就会出现
1 2 4 6(2 8 6 7)这种不合法序列。虽然满足所有ai <aj 但是存在i>j

注意:严格lis:lower_bound,非严格lis:upper_bound

        vector<int>lis;
        for(int j=i;j<n+i;j++)
        {
            auto it=upper_bound(all(lis),a[j]);
            if(it==lis.end())
                lis.pb(a[j]);
            else
                lis[it-lis.begin()]=a[j];
        }

LCS(最长公共序列)——O(n^2)

枚举一下就穿上裤子就走人
在这里插入图片描述
在这里插入图片描述

for(int i=1; i<=len1; ++i)
{
    for(int j=1; j<=len2; ++j)
    {
        if(s[i-1]==t[j-1])
            dp[i][j]=dp[i-1][j-1]+1;
        else
            dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
    }
}
cout<<dp[len1][len2]<<endl;

区间DP——O(n3

一般问题
把相邻符合条件的合并,来获得最优解

概念

区间类型动态规划是线性动态规划的拓展,它在分阶段划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。(例:f[i][j]=f[i][k]+f[k+1][j])

区间类动态规划的特点:

合并:即将两个或多个部分进行整合。
特征:能将问题分解成为两两合并的形式。
求解:对整个问题设最优值,枚举合并点,将问题分解成为左右两个部分,最后将左右两个部分的最优值进行合并得到原问题的最优值。

在这里插入图片描述
DP(n^3) DP+四边形不等式优化(n^2)
一般数据500还能跑跑,如果1e3基本凉了,要优化到O(n^2),要用到四边形不等式优化。暂时不会,再等等吧

cin>>n;
memset(dp,inf);///初始化dp数组
for(int i=1; i<=n; ++i)
    cin>>a[i],pre[i]=pre[i-1]+a[i],dp[i][i]=0;
for(int len=1; len<n; ++len) ///枚举区间长度
{
    for(int i=1; i+len<=n; ++i) ///枚举区间起点
    {
        int j=i+len;///根据起点得到合法终点
        for(int k=i; k<j; ++k) ///枚举最优分割点
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
        dp[i][j]+=pre[j]-pre[i-1];
    }
}
cout<<dp[1][n]<<endl;

树形DP——O(m)

算法核心
出发点:点对至少需要两个点。
枚举每条边的u和v,u是v父节点,v向下的最大直径+cost+u向上的最大直径
向下的最大直径,就是看最大跟次大,但是每次先加入当前点值,保证每次直径存在尾点权
向上的最大直径,u结点先更新点权跟向上最大简单路径,再更新v的最大直径点权和
子树计数

问题:
换根理解
u到v之前,把v向上的最长路径更新一下。
取决于u向下的第一长路径是否是v

  • 如果是,dp[v][0]=max(dp[u][0],dp[u][2])+1;
  • 如果不是,dp[v][0]=max(dp[u][0],dp[u][1])+1;

每个结点换根,只需要考虑dp[v][0]是否更新,换根并不是字面意思换根,只是把dp[v][0]除了第一次dfs模型向下路径,其他路径的最长更新dp[v][0]

在这里插入图片描述

vector<int>G[N];
int a[N],dp[N],ans;
void dfs(int u,int fat)
{
    dp[u]=a[u];
    for(auto v:G[u])
    {
        if(v!=fat)
        {
            dfs(v,u);
            dp[u]+=max(0,dp[v]);
        }
    }
    ans=max(ans,dp[u]);
}

换根-任意一点能到达的最远距离

const int N=5e5+5;
vector<pair<int,int> >G[N];
int dp[N][3],pot[N];
void dfs(int u,int fat)
{
    for(auto it:G[u])
    {
        int v=it.F,cost=it.S;
        if(v==fat)
            continue;
        dfs(v,u);
        if(dp[u][0]<=dp[v][0]+cost)
        {
            dp[u][1]=dp[u][0];
            dp[u][0]=dp[v][0]+cost;
            pot[u]=v;
        }
        else if(dp[u][1]<dp[v][0]+cost)
            dp[u][1]=dp[v][0]+cost;
    }
}
void dfs1(int u,int fat)
{
    for(auto it:G[u])
    {
        int v=it.F,cost=it.S;
        if(v==fat)
            continue;
        if(pot[u]==v)
            dp[v][2]=cost+max(dp[u][1],dp[u][2]);
        else
            dp[v][2]=cost+max(dp[u][0],dp[u][2]);
        dfs1(v,u);
    }
}

状压DP——O(3n

15 or 16可以枚举子集,以上就不行了
枚举子集
例如:1011
子集1010 1001 1000 0011 0010 0001
然后xor ,就可以得到子集的补集,合并取最小即可
在这里插入图片描述
枚举子集

    for(int i=1;i<(1<<n);i++)
    {
        dp[i]=get(i);
        for(int j=i;j;j=(j-1)&i)
            dp[i]=min(dp[i],dp[j]+dp[j^i]);
    }
    cout<<dp[ (1<<n)-1 ]<<endl;

概率DP

概率顺着推,期望逆着推

高维前缀和(sosdp)—— O(nlogn)

子集前缀和

    for(int i=0;i<22;i++)
        for(int j=0;j<(1<<22);j++)
            if(j&(1<<i))
                dp[j]+=dp[j^(1<<i)];

在这里插入图片描述

数位DP

状态考虑完全,都加入的dp数组中,参考YKW模板
注意:有的时候,把limit加入dp中,每次清空dp更快,有的时候,取消limit,一次清空更快。

int dp[20][2][2][unknow],a[20];
int dfs(int pos,bool limit,bool lead,int state)
{
    if(pos==0)///边界条件
        return true or false;
    if(limit==0&&dp[pos][limit][lead][state]!=-1)
        return dp[pos][limit][lead][state];
    int maxn=(limit?a[pos]:9);
    int ans=0;
    for(int i=0;i<=maxn;i++)
    {
        if(lead&&i==0)
            ans+=dfs(pos-1,limit&&i==maxn,true,state);
        else///确定状态的改变
            ans+=dfs(pos-1,limit&&i==maxn,false,state);
    }
    if(limit==0)
        dp[pos][limit][lead][state]=ans;
    return ans;
}
int query(int x)
{
    ///这里不 容易TLE——memset(dp,-1);
    int pos=0;
    while(x)
        a[++pos]=x%10,x/=10;
    return dfs(pos,true,true,0);///开始有限制,有前导0
}
///输入样例之前清空dp

### 动态规划算法模板 动态规划是一种解决多阶段决策过程最优化问题的方法,其核心思想是通过分解成更小的子问题来求解原问题。以下是基于 Python 的动态规划模板: #### 自顶向下的动态规划(带记忆化的递归) 这种方法适用于能够自然地划分为多个子问题的情况。 ```python from functools import lru_cache class Solution: def dp_function(self, state): @lru_cache(None) def dfs(current_state): if is_base_case(current_state): # 判断是否到达基本情况 return base_value[current_state] best_result = float('inf') # 初始化最优结果 for next_state in possible_next_states(current_state): result = dfs(next_state) + cost_to_move(current_state, next_state) best_result = min(best_result, result) return best_result return dfs(state) solution = Solution() result = solution.dp_function(initial_state) ``` 上述代码展示了如何利用递归来解决问题并缓存中间状态的结果以避免重复计算[^2]。 --- #### 自底向上的动态规划(迭代方式) 此方法通常用于可以显式定义状态转移方程的情形。 ```python def bottom_up_dp(): dp = [0] * (n + 1) # 定义dp数组 for i in range(1, n + 1): dp[i] = max(dp[i - 1], current_value(i)) # 更新当前状态的最佳值 return dp[n] result = bottom_up_dp() ``` 这种实现不需要额外的空间开销来进行函数调用栈管理,并且同样能保证每个子问题仅被计算一次。 --- ### 分治法模板 分治策略的核心在于将一个问题拆分成若干个规模较小但结构相似的子问题独立求解后再合并这些子问题的答案得到最终解答。下面是一个经典的汉诺塔问题作为例子展示分治的思想[^3]: ```python def hanoi(n, source, auxiliary, target): if n == 1: print(f"Move disk 1 from {source} to {target}") return hanoi(n - 1, source, target, auxiliary) # 将前n-1个盘子移动到辅助柱上 print(f"Move disk {n} from {source} to {target}") # 移动第n个盘子到目标柱 hanoi(n - 1, auxiliary, source, target) # 将剩余的n-1个盘子从辅助柱移到目标柱 hanoi(number_of_disks, 'A', 'B', 'C') ``` 该程序清晰体现了分治模式——把大问题逐步简化直至可以直接处理的小单元为止。 --- ### LaTeX 中编写伪代码样式设置 对于学术写作或者技术文档准备来说,在LaTeX里优雅呈现算法是非常重要的技能之一。这里提供了一个简单的配置方案以便于生成带有编号行以及边界线框装饰效果的伪代码环境[^1]: ```latex \makeatletter \newif\if@restonecol \makeatother \let\algorithm\relax \let\endalgorithm\relax \usepackage[linesnumbered, ruled, vlined]{algorithm2e} \begin{algorithm} \DontPrintSemicolon % 不打印结尾分号 \KwIn{输入参数描述...} \KwOut{输出结果说明...} \SetAlgoLined \For{$i \gets 1$ \KwTo $N$}{ 执行某些操作... \If{特定条件成立}{ 进一步执行逻辑分支... } } \caption{示例算法名称} \end{algorithm} ``` 这段脚本设置了基本框架使得后续撰写更加便捷高效的同时也保持视觉一致性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值