动态规划

108 阅读12分钟

动态规划

状态压缩DP

蒙德里安的梦想

[291. 蒙德里安的梦想 - AcWing题库]:

/*
    核心:先放横着的,再放竖着的
    f[i,j]表示已经将前i-1列摆好,且从第i-1列伸出到第i列的状态是j的所有方案
    
*/

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

typedef long long ll;

const int N=12, M=1<<N;

int n, m;
ll f[N][M];  
ll st[M];   //记录状态是否合法
vector<int> state[M]; //存储合法的状态

int main()
{
    while(cin>>n>>m, n||m)
    {
        
        //预处理1
        for(int i=0; i< 1<<n; i++)
        {
            st[i]=1;  //先假设状态合法 不可以放在最后加else 可以写成函数
            int cnt=0;
            for(int j=0; j<n; j++)
            {
                if(i>>j&1)  //第j位为1 判断上面连续的0是否是偶数个  合法状态
                {
                    if(cnt&1) st[i]=0; 
                    cnt=0;
                }
                else cnt++;
            }
            
            if(cnt&1) st[i]=0;//最后边界在再判断一次
            //else st[i]=1; 不可以,可能会改变上面的判断 有一个不合法就不合法
        }
        
        //预处理2  判断第i-2列伸出来的和第i-1列伸出去的是否冲突
        for(int i=0; i<1<<n; i++)
        {
            state[i].clear();
            for(int j=0; j<1<<n; j++)
                if((i&j)==0 && st[i|j]) // 交集为空 && 并集合法 --不能重合 空出来的连续偶数
                    state[i].push_back(j);
        }
        
      
        // dp
        memset(f, 0, sizeof f);
        f[0][0]=1;
        
        // 第1 2维枚举
        for(int i=1; i<=m; i++) //m列
            for(int j=0; j<1<<n; j++) // 遍历列的每一个状态
                for(auto k : state[j]) // 遍历第i-1列的状态k,如果“真正”可行,就转移
                    f[i][j]+=f[i-1][k];
                    
        cout<<f[m][0]<<endl; // 0开始到m-1  f[m][0]相当于m-1列都没伸出 即答案 m最大为11,故N+1=12 
    }
   
    return 0;
}

最短Hamilton路径

[91. 最短Hamilton路径 - AcWing题库]:

/*
    1.那些点被用过
    2.目前停在哪些点上
    
    f[i][j]:所有从0走到j 并且走过所有的点的情况是i 的所有路径集合
    
    i的状态:走过为1 没走过为0
    
    f[state][j] = f[state_k][k] + w[k][j]   0->j = 0->k + w[j][k]
    state_k = state 除掉j之后的集合 并且state要包含j
    
    注意:C++位运算的优先级比加减乘除的优先级低,所以遇到位运算和加减乘除一起的,要加个括号。
*/
#include <bits/stdc++.h>
using namespace std;

const int N = 20, M = 1<<N;

int n, w[N][N], f[M][N];

int main()
{
    cin>>n;
    for(int i=0; i<n; i++)
        for(int j=0; j<n; j++)
            cin>>w[i][j];
            
    memset(f, 0x3f, sizeof f);
    f[1][0]=0;
    
    //可以优化为  for(int i=1; i<1<<n; i+=2) 
    //循环时路径一定会从0开始,这样外层循环时间会减半 0位为1 一定是奇数
    // 第1 2维枚举
    for(int i=1; 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)
                        f[i][j]=min(f[i][j], f[i-(1<<j)][k] + w[j][k]);
            
    cout<<f[(1<<n)-1][n-1];
    return 0;
}

小国王

[1064. 小国王 - AcWing题库]:

/*
    f[i,j,st] 
    集合:所有只摆在前i行,已经摆了j个国王,并且第i行摆放的状态是st的所有方案的集合
    属性:Count

    状态计算: f[i-1][j-count(a)][b]
    已经摆完i-1排,i-1排的状态是b,已经摆了j-count(a)个国王的所有方案

*/

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

typedef long long ll;

const int N = 12, M = 1<<10;

int n, m;
ll f[N][N*N][M];
int cnt[M];
vector<int> st; //表示所有合法的状态
vector<int> head[M];//这个是每一状态所有可以转移到的其他状态

int check(int st) //判断一行中是否有两个相邻的国王
{
    if((st & st>>1)== 0) return 1; 
    else return 0;
}

int count(int st) //计算状态二进制里面1的个数,即国王的个数
{
    int res=0;
    for(int i=0; i<n; i++) res+=st>>i&1;
    return res;
}

int main()
{
    cin>>n>>m;

    // 预处理
    for(int i=0; i<1<<n; i++)  //i是状态
        if(check(i))   //这一行状态合法
        {
            st.push_back(i);
            cnt[i]=count(i); //合法状态中国王的数量
        }

    for(int i=0; i<st.size(); i++)
        for(int j=0; j<st.size(); j++)
        {
            int a=st[i], b=st[j];
            if((a&b)==0 && check(a|b)) //并集为空 && 交集不相邻
                head[i].push_back(j);  //a->b, b->a
        }


    // dp
    f[0][0][0]=1;
	// 第1 2 3维枚举
    for(int i=1; i<=n+1; i++)  
        for(int j=0; j<=m; j++) //国王的数量
            for(int a=0; a<st.size(); a++)//枚举所有状态
                for(int b:head[a])//枚举所有a能到的状态 能到a  即可以互相转化的状态
                {
                    int c=cnt[st[a]]; 
                    if(c<=j)   //状态a中 国王的数量比j小的时候才合法
                        f[i][j][a]+=f[i-1][j-c][b];    //  b->a
                }
    //前n行摆放了m个国王,并且第n行状态是0(没有国王)的所有方案数。 n+1=11 故N=12
    cout<<f[n+1][m][0];  //等价于下面的

    /*
    ll res=0;
    for(int i=0; i<st.size(); i++)  res+=f[n][m][i];
    cout<<res;
    */
    return 0;
}

玉米田

[327. 玉米田 - AcWing题库]:

/*
    f[i,s] 
    集合:所有只摆在前i行,并且第i行摆放的状态是s的所有摆放方案的集合
    属性:Count
    
    1.二进制不包含两个连续的1
    2.(a & b) == 0 没有交集
   
    
    已经摆完前i行,  且第i行的状态是a,第i-1行的状态是b的所有方案
    已经摆完前i-1行,且第i-1行的状态是b的所有摆放方案  f[i-1][b]

*/

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

typedef long long ll;

const int N = 14, M = 1<<N, mod = 1e8;

int n, m;
int g[N];  //每一行空地的状态
ll f[N][M];
vector<int> st; 
vector<int> head[M];

int check(int st) //判断一行中是否有两个相邻的国王
{
    for(int i=0; i<m; i++)  //m列 即m位
        if((st>>i & 1) && (st>>i+1 & 1)) 
            return 0; 
    return 1;
}


int main()
{
    cin>>n>>m;

    for(int i=1; i<=n; i++)
        for(int j=0; j<m; j++)
        {
            int x; cin>>x;
            g[i] += !x<<j;    //0 坏地 存进去1 后面方面&运算
        }
        
    // 预处理1 
    for(int i=0; i<1<<m; i++)
        if(check(i))
            st.push_back(i);
            
    // 预处理2
    for(int i=0; i<st.size(); i++)
        for(int j=0; j<st.size(); j++)
        {
            int a=st[i], b=st[j];
            if((a&b)==0)               // &优先级低 要加()
                head[i].push_back(j);
        }
    
    // dp
    f[0][0]=1;
    for(int i=1; i<=n+1; i++)
        for(int a=0; a<st.size(); a++)
            for(int b : head[a])
            {
                if(g[i] & st[a]) continue;  //与坏地不能有交集
                f[i][a] = (f[i][a] + f[i-1][b]) % mod;
            }
    
    cout<<f[n+1][0];
    return 0;
}

炮兵阵地

[292. 炮兵阵地 - AcWing题库]:

/*
    f[i,j,k] 
    集合:所有只摆在前i行,并且第i-1行摆放的状态是j, 第i行摆放的状态是k 的所有摆放方案的最大值
    属性:Max
    
    当前第i行和i-1行的状态只依赖于第i-2行的状态 枚举第i-2行的状态
    
    1.二进制不包含两个连续的1
    2.(a & b) == 0 没有交集
   
    check() 相邻两个1之间至少隔两个0
    a b c没有交集     a&b | a&c | b&c  == 0
    i-1行 i行 分别于a b没有交集  g[i-1]&a | g[i]&b  == 0 
    
    &1 二进制优化
    
    1e8的数组需要400MB

*/

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

typedef long long ll;

const int N = 110, M = 1<<10, mod = 1e8;

int n, m;
int g[N]; 
int cnt[M];
int f[2][M][M];
vector<int> st; 

int check(int st) //判断一行中是否有两个相邻的国王
{
    for(int i=0; i<m; i++)  //m列 即m位
        if((st>>i&1) && ((st>>i+1 & 1) || (st>>i+2&1)))
            return 0; 
    return 1;
}

int count(int st)
{
    int res=0;
    for(int i=0; i<m; i++) 
        if(st>>i&1)
            res++;
    return res;
}

int main()
{
    cin>>n>>m;

    for(int i=1; i<=n; i++)
        for(int j=0; j<m; j++)
        {
            char x; cin>>x;
            if(x=='H')
            g[i] += 1<<j;   
        }
        
    // 预处理  找出所有合法状态,并计算每个状态1的个数
    for(int i=0; i<1<<m; i++)
        if(check(i))
        {
            st.push_back(i);
            cnt[i]=count(i);
        }
 
        
    // dp
    for(int i=1; i<=n+2; i++)  
        for(int j=0; j<st.size(); j++) //j:i-1行
            for(int k=0; k<st.size(); k++)//k:i行
                for(int u=0; u<st.size(); u++) //u:i-2行
                {
                    int a=st[j], b=st[k], c=st[u]; // a:i-1  b:i c:i-2  
                    if(a&b | a&c |b&c) continue;
                    if(g[i-1]&a | g[i]&b) continue;
                    f[i&1][j][k] = max(f[i&1][j][k], f[i-1&1][u][j] + cnt[b]);  
                    
                }
    
    
    
    cout<<f[n+2&1][0][0];
    /*
    int res=0;
    for(int i=0; i<st.size(); i++)
        for(int j=0; j<st.size(); j++)
            res=max(res, f[n&1][i][j]);
    cout<<res;
    */
    return 0;
}

愤怒的小鸟

[524. 愤怒的小鸟 - AcWing题库]:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

#define x first
#define y second

typedef long long ll;
typedef pair<double, double> pdd;

const int N = 18, M = 1<<18;
const double eps=1e-8;

int n, m;
pdd q[N];
int path[N][N];
int f[M];

int cmp(double x, double y)
{
    if(fabs(x-y)<eps) return 0;
    if(x<y) return -1;
    return 1;
}


int main()
{
    int t; cin>>t;
    while(t--)
    {
        cin>>n>>m;
        for(int i=0; i<n; i++) cin>>q[i].x>>q[i].y;
        
        memset(path, 0, sizeof path);
        for(int i=0; i<n; i++)
        {
            path[i][i]=1<<i;
            for(int j=0; j<n; j++)
            {
                double x1=q[i].x, y1=q[i].y;
                double x2=q[j].x, y2=q[j].y;
                if(!cmp(x1, x2)) continue;
                double a = (y1/x1-y2/x2)/(x1-x2);
                double b = y1/x1-a*x1;
                if(cmp(a, 0)>=0) continue;
                
                int state=0;
                for(int k=0; k<n; k++)
                {
                    double x=q[k].x, y=q[k].y;
                    if(!cmp(a*x*x+b*x, y)) state+=1<<k;
                }
                path[i][j]=state;
            }
        }
        
        memset(f, 0x3f, sizeof f);
        f[0]=0;
        for(int i=0; i+1 < 1<<n; i++)
        {
            int x=0;
            for(int j=0; j<n; j++)
                if(!(i>>j&1))
                {
                    x=j;
                    break;
                }
            for(int j=0; j<n; j++)
                f[i|path[x][j]] = min(f[i|path[x][j]], f[i]+1);
        }
        
        cout << f[( 1 << n) -1]<<endl;
    }
    
    return 0;
} 

区间DP

主要问题

  1. 环形区间--->链状
  2. 区间DP+记录方案数
  3. 区间DP+高精度
  4. 二维区间DP

石子合并

[282. 石子合并 - AcWing题库]:

/*
    f[i][j]  将 i到j 这一段石子合并成一堆的方案的集合
             属性 Min
*/

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

const int N = 310;

int n, s[N];
int f[N][N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        cin>>s[i];
        s[i]+=s[i-1];   //处理前缀和
    }
    
    
    for(int len=2; len<=n; len++)      //枚举区间长度
        for(int i=1; i+len-1<=n; i++)  //枚举区间左端点  区间起点
        {
            int j=i+len-1;             //右端点          区间终点
            f[i][j]=0x3f3f3f3f;
            for(int k=i; k<j; k++)
                f[i][j]=min(f[i][j], f[i][k]+f[k+1][j] + s[j]-s[i-1]);
        }
    
    
    cout<<f[1][n];
    
    return 0;
}

环形石子合并

1068. 环形石子合并 - AcWing题库

/*
    长度为n的环 -> 长度为2n的链
*/
#include <bits/stdc++.h>
using namespace std;

const int N = 410, inf = 0x3f3f3f3f;

int n, w[N], s[N];
int f[N][N], g[N][N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        cin>>w[i];
        w[i+n]=w[i];
    }
    
    for(int i=1; i<=2*n; i++) s[i]=s[i-1]+w[i]; 
     
    memset(f, 0x3f, sizeof f);
    memset(g, -0x3f, sizeof g);
    
    for(int len=1; len<=n; len++)         //区间长度
        for(int i=1; i+len-1<=n*2; i++)   //左端点  注意长度为2n
        {
            int j=i+len-1;
            if(len==1) f[i][j]=g[i][j]=0; //右端点
            else  
                for(int k=i; k<j; k++)    //中间点
                {
                    f[i][j]=min(f[i][j], f[i][k]+f[k+1][j] + s[j]-s[i-1]);
                    g[i][j]=max(g[i][j], g[i][k]+g[k+1][j] + s[j]-s[i-1]);
                }
    }
    
    int minv=inf, maxv=-inf;
    for(int i=1; i<=n; i++)
    {
        minv=min(minv, f[i][i+n-1]);
        maxv=max(maxv, g[i][i+n-1]);
    }
    
    cout<<minv<<endl<<maxv;
    return 0;
}

能量项链

320. 能量项链 - AcWing题库

/*
    f[l][r]:所有将[l,r]合并成一个珠子的方式
    
    注意长度为n+1  边界共用 
    
    f[i][j]->f[i][k]+f[k][j]+w[i]*w[k]*w[j];
*/

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

const int N = 210;

int n, w[N],  f[N][N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) cin>>w[i], w[i+n]=w[i];
    
    for(int len=3; len<=n+1; len++) // 3---n+1
        for(int l=1; l+len-1<=2*n; l++)
        {
            int r=l+len-1;
            for(int k=l+1; k<r; k++)  //l+1---r-1 
                f[l][r]=max(f[l][r], f[l][k]+f[k][r]+w[l]*w[k]*w[r]);
        }
    
    int res=0;
    for(int i=1; i<=n; i++) res=max(res, f[i][i+n]); //i---n
    cout<<res;
    
    return 0;`
}

凸多边形的划分

1069. 凸多边形的划分 - AcWing题库

/*
    f[l,r]
    集合:所有将[l,l+1], [l+1, l+2],...,[r-1,r], [r,l] 这个多边形划分成三角形的方案   
    属性:Min ---要赋初始值 
    
    状态计算:f[l][r] = max(f[l][r], f[l][k]+f[k][r]+w[l]*w[r]*w[k])   环形
*/

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

typedef long long ll;

const int N = 55, M = 35;

int n, w[N];
ll f[N][N][M];  //第三维按位存数
ll temp[M];     // cpy数组存数

//高精度加法
void add(ll a[], ll b[])
{
    static ll c[M];
    memset(c, 0, sizeof c);
    ll t=0;
    for(int i=0; i<M; i++)
    {
        t+=a[i]+b[i];
        c[i]=t%10;
        t/=10;
    }
    memcpy(a, c, sizeof c);
}

//高精度乘法
void mul(ll a[], ll b)
{
    static ll c[M];   //相当于开了个全局变量,每次不用重新分配空间
    memset(c, 0, sizeof c);
    ll t=0;
    for(int i=0; i<M; i++)
    {
        t+=a[i]*b;
        c[i]=t%10;
        t/=10;
    }
    memcpy(a, c, sizeof c);
}

//高精度按位比较
int cmp(ll a[], ll b[])
{
    for(int i=M-1; i>=0; i--)
        if(a[i]>b[i]) return 1;
        else if(a[i]<b[i]) return -1;
    return 0;
}

//高精度按位输出
void print(ll a[])
{
    int k=M-1;
    while(k&&!a[k]) k--;
    while(k>=0) cout<<a[k--];
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) cin>>w[i];
    
    for(int len=3; len<=n; len++)
        for(int l=1; l+len-1<=n; l++) 
        {
            int r=l+len-1;
            f[l][r][M-1]=1;
            for(int k=l+1; k<r; k++) // l+1---r-1
            {
                memset(temp, 0, sizeof temp);
                temp[0]=w[l];
                mul(temp, w[k]);
                mul(temp, w[r]);
                add(temp, f[l][k]);
                add(temp, f[k][r]);
                if(cmp(f[l][r], temp)>0)
                    memcpy(f[l][r], temp, sizeof temp);
            }
        }
        
        print(f[1][n]);
        
        return 0;
}

加分二叉树

479. 加分二叉树 - AcWing题库

/*
    f[l,r] 所有中序遍历是[l,r]这一段的二叉树的集合
*/

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

const int N = 30;

int n, w[N];
int f[N][N];
int g[N][N]; //记录[l,r]区间内的根结点

void dfs(int l, int r)
{
    if(l>r) return ;
    int k=g[l][r];   
    cout<<k<<" ";   //前序遍历,根-左-右
    dfs(l, k-1);
    dfs(k+1, r);
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) cin>>w[i];
    
    for(int len=1; len<=n; len++)
        for(int l=1; l+len-1<=n; l++)
        {
            int r=l+len-1;
            if(len==1) f[l][r]=w[l], g[l][r]=l;  //叶节点 权值和根都是是本身
            else
            {
                for(int k=l; k<=r; k++)
                {
                    int left = k == l ? 1 :f[l][k-1];    //判断左右子树是否为空
                    int right = k == r ? 1 : f[k+1][r];
                    int s = left * right + w[k];
                    
                    if(f[l][r]<s) f[l][r]=s, g[l][r]=k; 
                }
            }
        }
       
        
    cout<<f[1][n]<<endl;
    dfs(1, n);
        
    return 0;
}

棋盘分割

321. 棋盘分割 - AcWing题库

/*
    f[x1,x2,y1,y2,k] 
    集合:将子矩阵(x1,y1) (x2,y2)切分成k部分的所有方案
    当前已经对棋盘进行了k次划分,且k次划分后选择的棋盘是 左上角为(x1,y1) 右下角为(x2,y2)
    
    属性:Min
    划分出来的 k+1个子矩阵的nσ2最小
*/

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

const int N = 16;

int n, m=8;
int s[N][N];
double f[N][N][N][N][N];
double X;

double get(int x1, int y1, int x2, int y2)
{
    double sum=s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1] - X;
    return sum*sum/n;
    
}

double dp(int x1, int y1, int x2, int y2, int k)
{
    double &v=f[x1][y1][x2][y2][k];
    if(v>=0) return v;
    if(k==1) return v=get(x1,y1,x2,y2);
    
    v=1e9;
    
    //横着切
    for(int i=x1; i<x2; i++)
    {
        v=min(v, dp(x1,y1,i,y2,k-1) + get(i+1,y1,x2,y2));
        v=min(v, dp(i+1,y1,x2,y2,k-1) + get(x1,y1,i,y2));
    }
    
    //竖着切
    for(int i=y1; i<y2; i++)
    {
        v=min(v, dp(x1,y1,x2,i,k-1) + get(x1,i+1,x2,y2));
        v=min(v, dp(x1,i+1,x2,y2,k-1) + get(x1,y1,x2,i));
    }
    return v;
    
}

int main()
{
    cin>>n;
    
    for(int i=1; i<=m; i++)
        for(int j=1; j<=m; j++)
        {
            cin>>s[i][j];
            s[i][j]+=s[i-1][j] + s[i][j-1] - s[i-1][j-1];
        }
        
    X=(double)s[m][m]/n;
    memset(f, -1, sizeof f);
    printf("%.3lf", sqrt(dp(1,1,8,8,n)));
    
    return 0;
}

树形DP

链式前向星

int h[N];  //head 邻接表存储图,有n个节点,所以需要n个头节点  存储以 i 为起点的  最后1条边  的下标
int e[M];  //表示第i条边的终点
int ne[M]; //next 表示与第i条边同起点的 上 一条边的下标
int w[M];  //存边长
int idx;   

void add(int a,int b,int c) //a到b点的距离为c
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

memset(h,-1,sizeof h);

for(int i=h[t];i!=-1;i=ne[i])  // t为父节点 找与t相连的边 按照存储顺序的倒序
    int j=e[i];

a     b     e[idx]=b     ne[idx]=h[a]    h[a]=idx++
1     2    e[0] = 2;     ne[0] = -1;      h[1] = 0;
1     3    e[1] = 3;     ne[1] =  0;      h[1] = 1;
1     5    e[2] = 5;     ne[2] =  1;      h[1] = 2;
2     4    e[3] = 4;     ne[3] = -1;      h[2] = 3;


没有上司的舞会

285. 没有上司的舞会 - AcWing题库

/*
集合:f[u][0] 所有从以u为根的子树中选择,并且不选u这个点的方案
      f[u][1] 所有从以u为根的子树中选择,并且选u这个点的方案
*/

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

const int N=6010, M=2*N;

int n, ha[N];
int h[N], e[M], ne[M], idx;
int fa[N], f[N][2];

void add(int a, int b)
{
    e[idx]=b, ne[idx]=h[a], h[a]=idx++;
}

void dfs(int u)
{
    f[u][1]=ha[u];   //当前选u,需要加上u的幸福度

    for(int i=h[u]; i!=-1; i=ne[i])
    {
        int j=e[i];
        dfs(j);    //回溯,用该值开始新遍历

        f[u][1]+=f[j][0];               //选u,不选子树
        f[u][0]+=max(f[j][0], f[j][1]); //不选u,子树可选可不选
    }
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) cin>>ha[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子节点
        fa[a]=1;    //标记子节点
    }

    int root=1;
    while(fa[root]) root++;  //找根节点
    dfs(root);

    cout<<max(f[root][0],f[root][1]);
}

计数类DP

整数划分

900. 整数划分 - AcWing题库

/*
方法1   类似完全背包

    f[i,j] i是体积 j是总容量
    
    集合: 从1~i中选,恰好是j的集合
    
    f[i,j]      = f[i-1,j]   + f[i-1, j-i]  + ... + f[i-1, j-si]
    f[i,j -i]   = f[i-1,j-i] + f[i-1, j-2i] + ... + f[i-1, j-si]
    
    f[i,j] = f[i-1,j] + f[i,j -i]
    
*/
#include <bits/stdc++.h>
using namespace std;

const int N=1010, mod=1e9+7;

int n,f[N];

int main()
{
    cin>>n;
    
    f[0]=1;
    for(int i=1; i<=n; i++)
        for(int j=i; j<=n; j++)
            f[j]=(f[j]+f[j-i])%mod;

    cout<<f[n];
    return 0;
}
/*
方法2
    f[i,j]
    集合:所有总和是i,并且恰好表示成j个数和的方案
    属性:count
    
    状态计算:
    最小值等于1 | 最小值大于1
    f[i-1][j-1] | f[i-j][j] (每个数都-1)
*/


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

const int N=1010, mod=1e9+7;

int n,f[N][N];

int main()
{
    cin>>n;
    f[0][0]=1;

    for(int i=1; i<=n; i++)
        for(int j=1; j<=i; j++) //和<=i个1
            f[i][j]=(f[i-1][j-1]+f[i-j][j])%mod;


    int res=0;
    for(int i=1; i<=n; i++) res=(res+f[n][i]) %mod;

    cout<<res;
    return 0;
}

数位DP

计数问题

338. 计数问题 - AcWing题库

线性DP

数字三角形

898. 数字三角形 - AcWing题库

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

const int N=510, INF=1e9;

int n,a[N][N],f[N][N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=i; j++)
            cin>>a[i][j];

    //初始化
    for(int i=1; i<=n; i++)
        for(int j=0; j<=i+1; j++) //初始化时注意j的边界 0~—i+1;
            f[i][j]=-INF;         // 下一行最右边的的会用到上一行的右边 

    f[1][1]=a[1][1];

    for(int i=1; i<=n; i++)
        for(int j=1; j<=i; j++)
            f[i][j]=max(f[i-1][j-1], f[i-1][j])+a[i][j];

    int res=-INF;
    for(int i=1; i<=n; i++)
        res=max(res, f[n][i]); //取最后一行的最大值

    cout<<res;
    return 0;
}
// 倒序不用考虑边界问题
#include<bits/stdc++.h>
using namespace std;

const int N=510;
int f[N][N];
int n;

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            cin>>f[i][j];
        }
    }

    for(int i=n;i>=1;i--){
        for(int j=i;j>=1;j--){
            f[i][j]=max(f[i+1][j],f[i+1][j+1])+f[i][j];
        }
    }
    cout<<f[1][1]<<endl;
}

摘花生

1015. 摘花生 - AcWing题库

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

int t,m,n;
int a[110][110], f[110][110];

int main()
{
    cin>>t;
    while(t--)
    {
        cin>>n>>m;

        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
            {
                cin>>a[i][j];
                //dp[i][j]=0;  每次都从头跑一遍,更新过了,不影响
            }

        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                f[i][j]=max(f[i-1][j], f[i][j-1]) + a[i][j];

        cout<<f[n][m]<<endl;   
    }

}

// 滚动数组优化
#include<cstring>
#include<iostream>
using namespace std;

const int N = 105;
int a[2][N], f[2][N], q, n, m;

int main()
{
    cin >> q;
    while(q--){
        cin >> n >> m;

        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                cin >> a[i&1][j];
                f[i&1][j] = max(f[i&1][j-1], f[(i-1)&1][j]) + a[i&1][j];
            }
        }
        cout << f[n&1][m] << endl;

        memset(f, 0, sizeof f);
    }
}

最低通行费

1018. 最低通行费 - AcWing题库

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

int n;
int a[110][110], f[110][110];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++) 
            cin>>a[i][j];

//需要先把所有状态初始化为正无穷,初始化状态的起点(dp求最小值必须要的步骤)
//状态转移时的越界判断        
    memset(f, 0x3f, sizeof f);

//法1
    f[1][1]=a[1][1];
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
        {
            f[i][j]=min(f[i][j], f[i-1][j]+a[i][j]);
            f[i][j]=min(f[i][j], f[i][j-1]+a[i][j]);
            //f[i][j]=min(f[i-1][j], f[i][j-1]) +a[i][j]; 
            //这样写f[0][1]和f[1][0]会被考虑 都是最大值
        }

//法2
    f[0][1]=f[1][0]=0; //边界初始化为0
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            f[i][j]=min(f[i-1][j], f[i][j-1]) +a[i][j]; 

    cout<<f[n][n];

}

方格取数

1027. 方格取数 - AcWing题库

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

int n;
int w[11][11], f[21][11][11]; 

int main()
{
    cin>>n;
    int a,b,c;
    while( cin>>a>>b>>c, a||b||c)
        w[a][b]=c;

    //f[k][i1][i2] 
    for(int k=2; k<=2*n; k++)  //k表示i+j 即行走的步数
        for(int i1=1; i1<=n; i1++)
            for(int i2=1; i2<=n; i2++)
            {
                int j1=k-i1, j2=k-i2;

                if(j1>=1&&j1<=n && j2>=1&&j2<=n) //保证j合法
                {
                    int &x=f[k][i1][i2]; //引用 地址一样 x随着f改变而改变

                    int t=w[i1][j1];
                    if(i1!=i2) t+=w[i2][j2];   //重合的时候加一次 不重合两个都加

                    x=max(x, f[k-1][i1-1][i2-1]+t); //i1-1,j1 i2-1,j2
                    x=max(x, f[k-1][i1-1][i2]+t);   //i1-1,j1 i2,j2-1
                    x=max(x, f[k-1][i1][i2-1]+t);   //i1,j1-1 i2-1,j2
                    x=max(x, f[k-1][i1][i2]+t);     //i1,j1-1 i2,j2-1
                }
            }

    cout<<f[2*n][n][n];
    
    return 0;
}

最长上升子序列

AcWing 895. 最长上升子序列 - AcWing

//集合: 所有以第i个数结尾的上升子序列

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

const int N=1010;
int n,a[N],f[N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];

    for(int i=1; i<=n; i++)
    {
        f[i]=1;//只有a[i]一个数 为1
        for(int j=1; j<i; j++)
            if(a[j]<a[i])
                f[i]=max(f[i],f[j]+1);// 此处f[i]不能改为1
    }

    int res=0;
    for(int i=1; i<=n; i++)
        res=max(f[i],res);    
    cout<<res;

    return 0;
}

最长上升子序列 II (贪心+二分)

896. 最长上升子序列 II - AcWing题库

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

const int N = 100010;

int n, a[N], q[N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];
    
    q[0]=-2e9;
    int len=0;
    
    for(int i=1; i<=n; i++)
    {
        int l=0, r=len;
        while(l<r)
        {
            int mid=r+l+1>>1;
            if(q[mid]<a[i]) l=mid;
            else r=mid-1;
        }
        q[r+1]=a[i];
        len=max(len, r+1);
    }
    
    cout<<len;
    return 0;
}

最大上升子序列和

1016. 最大上升子序列和 - AcWing题库

+1+1 改成 +a[i]+a[i] 即可

//集合: 所有以第i个数结尾的上升子序列

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

const int N=1010;
int n,a[N],f[N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];

    for(int i=1; i<=n; i++)
    {
        f[i]=a[i];//只有a[i]一个数 为1
        for(int j=1; j<i; j++)
            if(a[j]<a[i])
                f[i]=max(f[i],f[j]+a[i]);// 此处f[i]不能改为1
    }

    int res=0;
    for(int i=1; i<=n; i++)
        res=max(f[i],res);    
    cout<<res;

    return 0;
}

拦截导弹

AcWing 1010. 拦截导弹 - AcWing

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

const int N = 10010;

int n=0, a[N], f1[N], f2[N];

int main()
{
    int x;
    while(cin>>x) a[++n]=x;


    //for(int i=0; i<=n; i++) cout<<a[i]<<endl;

    int res1=0;
    int res2=0;


    for(int i=1; i<=n; i++)
    {
        f1[i]=1;
        f2[i]=1;
        for(int j=1; j<i; j++)
            if(a[i]<=a[j])
                f1[i]=max(f1[i], f1[j]+1); //res1=max(res1, f1[i]);  

            else        
                f2[i]=max(f2[i], f2[j]+1);// res2=max(res2, f2[i]);

        res1=max(res1, f1[i]);
        res2=max(res2, f2[i]);
        //不能写在里面, 只有一个数的时候res不会更新为0,应该为1
    }

    cout<<res1<<endl<<res2;

    return 0;
}

导弹防御系统

187. 导弹防御系统 - AcWing题库

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

const int N = 10010;

int n, a[N], up[N], down[N];
int ans;

//前u个导弹 上升序列个数su 下降序列个数sd 
void dfs(int u, int su, int sd)
{
    if(su+sd>=ans) return;  //剪枝
    if(u==n)   
    { 
        ans=su+sd;
        return;
    }

    //情况1:考虑用上升拦截系统来拦截第u个导弹  
    //将当前数放在上升子序列中

    //如果当前已有的上升拦截系统的高度都大于第u个导弹高度,则重新开一套系统
    //否则,则由当前低于第u个导弹最高拦截系统来负责拦截

    int k=0;
    while(k<su && up[k]>a[u]) k++; //找到了有这么个拦截系统,即上升子序列

    int t=up[k];  //t记录下up[k]  用于dfs回溯的时候恢复现场 

    up[k]=a[u];
    if(k<su) dfs(u+1, su, sd);    //放在当前序列
    else dfs(u+1, su+1, sd);      //新开一个序列

    up[k]=t;   //恢复现场


    //情况2:考虑用下降拦截系统来拦截第u个导弹
    //将当前数放在下降子序列中
    k=0;
    while(k<sd && down[k]<a[u]) k++;
    t=down[k];
    down[k]=a[u];
    if(k<sd) dfs(u+1, su, sd);
    else dfs(u+1, su, sd+1);
    down[k]=t;

}

int main()
{
    while(cin>>n && n)
    {
        for(int i=0; i<n; i++) cin>>a[i];

        ans=n;
        dfs(0, 0, 0);
        cout<<ans<<endl;
    }

    return 0;
}

最长公共子序列

897. 最长公共子序列 - AcWing题库

//集合:所有在 第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现 的子序列

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

const int N = 1010;

int n, m, f[N][N];
char a[N], b[N];

int main()
{
    cin>>n>>m;
    cin>>a+1>>b+1;
    
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
            if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
            else f[i][j]=max(f[i-1][j], f[i][j-1]);
    
    cout<<f[n][m];
    
    return 0;
}

最长公共上升子序列

272. 最长公共上升子序列 - AcWing题库

/*
    f[i][j]:所有由第一个序列的前i个字母,第二个序列的前j个字母构成的
             且以b[j]结尾的公共上升子序列

*/

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

const int N = 3010;

int n, a[N], b[N];
int f[N][N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];
    for(int i=1; i<=n; i++) cin>>b[i];

    for(int i=1; i<=n; i++)
    {
        int maxv=1;
        for(int j=1; j<=n; j++)
        {
            f[i][j]=f[i-1][j];
            if(a[i]==b[j]) f[i][j]=max(f[i][j], maxv);
            if(a[i]>b[j]) maxv=max(maxv, f[i-1][j]+1); 
        }
    }
    
    int res=0;
    for(int i=1; i<=n; i++) res=max(res, f[n][i]);
    cout<<res;


    return 0;
}


/*   优化前会超时
每次循环求得的maxv是满足 a[i]>b[k] 的f[i-1][k]+1的前缀最大值。

for (int i = 1; i <= n; i ++ )
{
    for (int j = 1; j <= n; j ++ )
    {
        f[i][j] = f[i - 1][j];
        if (a[i] == b[j])
        {
            int maxv = 1;
            for (int k = 1; k < j; k ++ )
                if (a[i] > b[k])
                    maxv = max(maxv, f[i - 1][k] + 1);
            f[i][j] = max(f[i][j], maxv);
        }
    }
}
*/

最短编辑距离

902. 最短编辑距离 - AcWing题库

//集合:所有将a[1~i]变成b[1~j]的操作方式

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

const int N=15, M=1010;

int n,m,f[N][N];
char str[M][N];

int dis(char a[],char b[])
{
    int la=strlen(a+1), lb=strlen(b+1);
    
    for(int i=0; i<=la; i++) f[i][0]=i;
    for(int i=0; i<=lb; i++) f[0][i]=i;
    
    for(int i=1; i<=la; i++)
        for(int j=1; j<=lb; j++)
        {
            f[i][j]=min(f[i-1][j]+1, f[i][j-1]+1);
            f[i][j]=min(f[i][j], f[i-1][j-1] + (a[i]!=b[j]) );
        }
    return f[la][lb];
}

int main()
{
    cin>>n>>m;
    for(int i=0; i<n; i++) cin>>str[i]+1;

    while(m--)
    {
        char s[N];
        int limit;
        cin>>s+1>>limit;

        int res=0;
        for(int i=0; i<n; i++)
            if( dis(str[i],s) <= limit)
                res++;
        cout<<res<<endl;
    }

    return 0;
}

编辑距离

899. 编辑距离 - AcWing题库

//集合:所有将a[1~i]变成b[1~j]的操作方式

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

const int N=15, M=1010;

int n,m,f[N][N];
char str[M][N];

int dis(char a[],char b[])
{
    int la=strlen(a+1), lb=strlen(b+1);
    
    for(int i=0; i<=la; i++) f[i][0]=i;
    for(int i=0; i<=lb; i++) f[0][i]=i;
    
    for(int i=1; i<=la; i++)
        for(int j=1; j<=lb; j++)
        {
            f[i][j]=min(f[i-1][j]+1, f[i][j-1]+1);
            f[i][j]=min(f[i][j], f[i-1][j-1] + (a[i]!=b[j]) );
        }
    return f[la][lb];
}

int main()
{
    cin>>n>>m;
    for(int i=0; i<n; i++) cin>>str[i]+1;

    while(m--)
    {
        char s[N];
        int limit;
        cin>>s+1>>limit;

        int res=0;
        for(int i=0; i<n; i++)
            if( dis(str[i],s) <= limit)
                res++;
        cout<<res<<endl;
    }

    return 0;
}

怪盗基德的滑翔翼

1017. 怪盗基德的滑翔翼 - AcWing题库

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

const int N=110;
int n, a[N], f[N];

int main()
{
    int t; cin>>t;
    while(t--)
    {
        cin>>n;
        for(int i=1; i<=n; i++)
            cin>>a[i];

        int res=0;

        ////向左最长上升
        //正向求解  往左边跳  从左到右最长上升
        for(int i=1; i<=n; i++)
        {
            f[i]=1;
            for(int j=1; j<i; j++)
                if(a[i]>a[j])
                    f[i]=max(f[i],f[j]+1);

            res=max(res, f[i]);
        }

        //向右最长下降
        //正向求解  往右边跳  从右到左最长上升
        for(int i=n; i>0; i--)
        {
            f[i]=1;
            for(int j=n; j>i; j--)
                if(a[i]>a[j])
                    f[i]=max(f[i], f[j]+1);

            res=max(res, f[i]);
        }
        
        /*
//或者从左向右最长下降子序列
        for(int i=1; i<=n; i++)
        {
            f[i]=1;
            for(int j=1; j<i; j++)
                if(a[i]<a[j])
                    f[i]=max(f[i],f[j]+1);

            res=max(res, f[i]);
        }
        */


        cout<<res<<endl;
    }
    return 0;
}

登山

1014. 登山 - AcWing题库

类似上题

不能正向求最长下降子序列!!! 如果直接把正向结果与最长上升序列长度相加, 得到的是 从前往后以a[i]a[i]为结尾的最长上升序列长度 ++ 从前往后以a[i]a[i]为结尾的最长下降序列长度 而题目所求的可以转化为 从前往后以a[i]a[i]为结尾的最长上升长度 ++ 从后往前以a[ia[i]为结尾的最长上升长度。两者是不同的。

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

const int N=1010;
int n, a[N], f1[N], f2[N];

int main()
{

    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];

    for(int i=1; i<=n; i++)
    {
        f1[i]=1;
        for(int j=1; j<i; j++)
            if(a[i]>a[j])
                f1[i]=max(f1[i],f1[j]+1);
    }

    for(int i=n; i>0; i--)
    {
        f2[i]=1;
        for(int j=n; j>i; j--)
            if(a[i]>a[j])
                f2[i]=max(f2[i], f2[j]+1);
    }

    int res=0;
    for(int i=1; i<=n; i++)   //左右相加
        res=max(res, f1[i]+f2[i]-1);
    cout<<res<<endl;

    return 0;
}

合唱队形

482. 合唱队形 - AcWing题库

类似上题

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

const int N=1010;
int n, a[N], f1[N], f2[N];

int main()
{

    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];

    for(int i=1; i<=n; i++)
    {
        f1[i]=1;
        for(int j=1; j<i; j++)
            if(a[i]>a[j])
                f1[i]=max(f1[i],f1[j]+1);
    }

    for(int i=n; i>0; i--)
    {
        f2[i]=1;
        for(int j=n; j>i; j--)
            if(a[i]>a[j])
                f2[i]=max(f2[i], f2[j]+1);
    }

    int res=0;
    for(int i=1; i<=n; i++)   //左右相加
        res=max(res, f1[i]+f2[i]-1);
    cout<<n-res<<endl;       //最少出列=总数-保留的最长的

    return 0;
}

友好城市

1012. 友好城市 - AcWing题库

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

const int N=5010;
int n, a[N], f[N];

struct node {
    int x, y;
}s[N];

bool cmp(node a, node  b)
{
    return a.x<b.x;
}
int main()
{

    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>s[i].x>>s[i].y;

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


    //一岸从小到大排序,另一岸求最大上升子序列
    //保存下来另一岸的坐标
    for(int i=1; i<=n; i++)
        a[i]=s[i].y;


   
    for(int i=1; i<=n; i++)
    {
        f[i]=1;
        for(int j=1; j<i; j++)
            if(a[i]>a[j])
                f[i]=max(f[i],f[j]+1);
    }
    

/*
    若j从0开始 就不用把f[i]=1,下面会更新
    
    for(int i=1; i<=n; i++)
        for(int j=0; j<i; j++)
            if(a[i]>a[j])
                f[i]=max(f[i],f[j]+1);
*/
       

    int res=0;
    for(int i=1; i<=n; i++)
        res=max(res, f[i]);
    cout<<res;

    return 0;
}
#include <bits/stdc++.h>
using namespace std;

const int N=5010;
int n, a[N], f[N];

int main()
{

    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];

    int res=0;

    for(int i=1; i<=n; i++)
    {
        f[i]=a[i]; //这里不是1
        for(int j=1; j<i; j++)
            if(a[i]>a[j])
                f[i]=max(f[i],f[j]+a[i]);

        res=max(res, f[i]);
    }

    /*
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<i; j++)  //从0开始就不用初始化,利用f[0]=0会在下面进行初始化
            if(a[i]>a[j])
                f[i]=max(f[i],f[j]+a[i]);

        res=max(res, f[i]);
    }
    */


    cout<<res;

    return 0;
}

杨老师的照相排列

271. 杨老师的照相排列 - AcWing题库

//集合:所有将a[1~i]变成b[1~j]的操作方式

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

const int N=1010;

int n, m, f[N][N];
char a[N], b[N];

int main()
{
    cin>>n>>a+1;
    cin>>m>>b+1;
    
    for(int i=0; i<=m; i++) f[0][i]=i;
    for(int i=0; i<=n; i++) f[i][0]=i;//初始化
    
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
        {
            /*
            f[i][j]=min(f[i-1][j]+1, f[i][j-1]+1);  //插入 删除
            if(a[i]==b[j]) f[i][j]=min(f[i][j], f[i-1][j-1]); //替换
            else f[i][j]=min(f[i][j], f[i-1][j-1]+1);
            */
            // 两中均可以 下面慢点
            if(a[i]==b[j]) f[i][j]=f[i-1][j-1];
            else f[i][j]=min({f[i-1][j]+1, f[i][j-1]+1, f[i-1][j-1]+1});
        }
    
    cout<<f[n][m];
    return 0;
}