当前位置: 首页 > news >正文

「DP」专题训练(持续更新中)

文章目录

  • 一、前言
  • 二、算法
    • 1.线性DP
      • <1>(最长上升子序列 II)
      • <2>(A - Frog 1)
      • <3>(B - Frog 2)
      • <4>(Longest Simple Cycle)
      • <5>(C - Vacation)
      • <6>(F - LCS)
      • <6>(最长公共子序列 II)
    • 2.背包DP
      • <1>(「木」迷雾森林)
      • <2>(CSL分苹果)
      • <3>(D - Knapsack 1)
      • <4>(E - Knapsack 2)
    • 3.图论DP / 记忆化搜素
      • <1>(G - Longest Path)
      • <2>(H - Grid 1)
    • 4.期望DP
      • <1>(I - Coins)
      • <2>(J - Sushi)
    • 3.区间DP
    • 4.数位DP
    • 5.状压DP
  • 三、总结


一、前言

DP大集合,持续更新中
顺序按照难度递增,但均属于基础DP,全需掌握。
所有字母标序题目均属于 Educational DP Contest(传送门)
⚠️注意看代码注释,均是博主易错点


二、算法

1.线性DP

问题的最优解包含着它的子问题的最优解。即不管前面的策略如何,此后的策略必须是基于当前状态(由上一次决策产生)的最优决策。(异或,除余都不适用常规动态规划)

  • 结合原问题和子问题确定状态:
    (1)描述位置的:前(后)i单位,第i到第j单位,坐标为(i,j)第i个之前(后)且必须取第i个等
    (2)描述数量的:取i个,不超过i个,至少i个等
    (3)描述对后有影响的:状态压缩的,一些特殊的性质
  • 确定转移方程:
    (1)检查参数是否足够
    (2)分情况:最后一次操作的方式,取不取,怎么样取
    (3)初始边界是什么
    (4)注意无后效性。(比如说,求A求要求B,求B就要求C,求C就要求A,这就不符合无后效性。)
  • 需不需要优化
  • 确定编程实现方式
    (1)递推
    (2)记忆化搜索

<1>(最长上升子序列 II)

题解:
相比较I,II的数据增大了100倍,所以无法直接采用o(n * n)的算法,而是用了一种o(n * log(n))的算法,贪心➕二分。
数组q[i]储存长度为i的序列的最小的结尾元素,最终输出q的长度即可。
代码:

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
#include<queue>
#include<utility>

using namespace std;
#define int long long
const int N = 1e5+10;
int n;
int a[N],q[N];

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

   int res = 0;
   q[++res] = a[1];
   //可以简单看出q数组是递增的

   for (int i = 2; i <= n; i++) {
       if(a[i] > q[res])q[++res] = a[i];
       //如果比末尾值大,继续加入即可
       else {
       int l = 1,r = res;
       while(l < r) {
       //二分
           int mid = (l+r)/2;
           if(q[mid] < a[i])l = mid+1;
           else r = mid;
       }
       //找出第一个大于等于a[i]的值
       //把那个位置的q[r]变成a[i]
       q[r] = a[i];}
   }
   cout << res << endl;

   return 0;
}

<2>(A - Frog 1)

题解:
N块石头,给出每块石头的高度是h[i],一只青蛙此刻在第一块石头上,每次可以跳到下一个石头或下下个石头,需要耗费|h[i]-h[j]|的力气,问跳到最后一块石头所需要的最小力气。
一维线性dp,每一个当前状态是由前俩个状态的较优态转化而来。
代码:

#include<bits/stdc++.h>

using namespace  std;
#define int long long
const int N = 1e5+10;
int h[N];int n;
int dp[N];

void solve() {
   cin >> n;
   for (int i = 1; i <= n; i++) cin >> h[i];
   dp[0] = 0,dp[1] = 0;
   for (int i = 2; i <= n; i++) {
       if(i == 2) dp[i] = abs(h[i]-h[i-1]);
       //注意初始状态,毕竟青蛙其实不是从第0块石头开始跳的
       else dp[i] = min(dp[i-1]+abs(h[i]-h[i-1]),dp[i-2]+abs(h[i]-h[i-2]));
   }cout << dp[n];
}

signed main(){
   ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
   int t = 1;
  // cin>>t;
   while(t--){
       solve();
   }
   return 0;
}

<3>(B - Frog 2)

题解:
还是这只青蛙,这次它不止可以跳到俩块之外(也就是下下块)的石头,它最远可以跳到k块之外的石头,当然比k小的距离的石头也都可以,所耗费的还是|h[i]-h[j]|。
查看数据范围,发现可以通过俩次for循环计算dp,比较所有能跳过来的dp,赋最小值。
代码:

#include<bits/stdc++.h>

using namespace  std;
#define int long long
const int N = 1e5+10;
int h[N];int n,k;
int dp[N];

void solve() {
   cin >> n >> k;
   for (int i = 1; i <= n; i++) {
       cin >> h[i];dp[i] = 1000000010;
       //注意数据范围,dp有可能是所有h的和这么大
   }
   dp[0] = 0,dp[1] = 0;
   for (int i = 2; i <= n; i++) {
       int p = 1;
       for (int j = i-1; j >= max((i-k),p);j--) {
           dp[i] = min(dp[i],dp[j]+abs(h[j]-h[i]));
       }//cout << dp[i] << ' ';
   }cout << dp[n];
}

signed main(){
   ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
   int t = 1;
  // cin>>t;
   while(t--){
       solve();
   }
   return 0;
}

<4>(Longest Simple Cycle)

题解:
给定n段线段,c数组储存每条线段的节点数,a数组表示每条线段的头节点与前一条线段相连的位置,b数组表示每条线段的尾节点与前一条线段相连的位置,第一条线段不与前面相连,输入-1。求一个循环的最大节点数。
考虑以每一条线段作为结尾时的最大节点数,对于前面一个状态,有俩种可能达到它的最大节点数,使用以前面一条线段作为结尾时的最大节点数,或者使用只用前面一条线段。具体见代码。
代码:

#include<bits/stdc++.h>

using namespace std;
#define int long long
const int N = 1e5+10;
int a[N],b[N],c[N];
int dp[N];
//以这条线段为结尾

void solve() {
   int n;
   cin >> n;
   for (int i = 0; i < n; i++) cin >> c[i];
   for (int i = 0; i < n; i++) cin >> a[i];
   for (int i = 0; i < n; i++) cin >> b[i];
   int res = 0;
   for(int i = 1; i < n; i++) {
       int ls = abs(a[i]-b[i]);
       if(a[i] == b[i]) dp[i] = c[i]+1;
       //如果向前的首尾相同,相当于不能使用前面的线段
       else dp[i] = c[i]+max(ls+1,dp[i-1]-ls+1);
       //考虑使用前面那一截线段,或者前面的前面的所有线段
       //前面的所有线段是根据前面一个的最优状态进行推算的
       res = max(res,dp[i]);
       //     cout << dp[i] << ' ';
   }
   cout << res << endl;
   return ;
}

signed main() {
   ios::sync_with_stdio(false);
   cin.tie(0);
   cout.tie(0);
   int t=1;
   cin >> t;
   while(t--) {
       solve();
   }
   return 0;
}

<5>(C - Vacation)

题解:
N天,每天有三种活动可以增加不相同的开心值,要求不能连续俩天进行同一项活动,求最后的开心最大值。
二维线性DP,每一天的状态可以从前一天的进行了另外俩种活动的情况转化而来,选择最大值即可。
代码:

#include <iostream>
#include <set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long
const int N = 1e5+10;
int n;
int a[N],b[N],c[N];
int dp[N][5];

void solve(){
   cin >> n;
   for (int i = 1 ; i <= n; i++) {
       for (int j= 1; j <= 3; j++) {
           if(j == 1) cin >> a[i];
           else if(j == 2) cin >> b[i];
           else cin >> c[i];
       }
   }
   for (int i = 1; i <= n; i++) {
       dp[i][1] = max(dp[i-1][2],dp[i-1][3])+a[i];
       dp[i][2] = max(dp[i-1][1],dp[i-1][3])+b[i];
       dp[i][3] = max(dp[i-1][1],dp[i-1][2])+c[i];
   }int ans = max(dp[n][1],dp[n][2]);
   ans = max(ans,dp[n][3]);
   cout << ans << endl;
}

signed main(){
   ios::sync_with_stdio(false);
   cin.tie(0);cout.tie(0);
   int t = 1;
//   cin >> t;
   while(t--) {
       solve();
   }
   return 0;
}

<6>(F - LCS)

<6>(最长公共子序列 II)

最长公共子序列
题解:
查找俩个字符串的最长公共子序列并输出,难点在于需要输出,可以从最后一位倒数回去,相同的字符则输出。
代码:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long
const int N = 3100;
string s,t;
int dp[N][N];

void solve() {
  cin >> s >> t;
  s = ' '+s; t = ' '+t;
  for (int i = 1; i < s.length(); i++) {
      for (int j = 1; j < t.length(); j++) {
          if(s[i] == t[j]) dp[i][j] = dp[i-1][j-1]+1;
          else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
      }
  }int ls = s.length()-1; int lt = t.length()-1;
//    for (int i = 0; i < ls; i++) {
//        for (int j = 0; j < lt; j++) {
//            cout << dp[i][j] << ' ';
//        }cout << endl;
//    }
  string ans;
  while(1) {
      if(s[ls] == t[lt]) {
          ans = s[ls]+ans;
          ls--;lt--;
      }else {
          if(dp[ls-1][lt] > dp[ls][lt-1]) ls--;
          else lt--;
      }
    //  cout << ls << ' ' << lt << endl;
      if(ls <= 0 || lt <= 0) break;
  }

  cout << ans << endl;
}

signed main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  int _ = 1;
  //   cin>>_;
  while(_--){
      solve();
  }
  return 0;
}

2.背包DP

  1. 01背包
    给定n件重量为w,价值为v的物品,问一个可承载m重量的背包最多能获得多少价值,是每件东西的取存问题,所以是01背包
   for (int i = 1; i <= n; i++) {
       for (int j = 1; j <= m; j++) {
           if(j < w[i]) dp[i][j] = dp[i-1][j]; //放不下这件物品
           else dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
           //选取放和不放之间的较优态
       }
   }
    for (int i = 1; i <= n; i++) {
       for (int j = m; j >= w[i]; j--) {
           dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
           //在存放空间j下能获得的最大价值
       }
   }
  1. 完全背包
    还是n类重量为w,价值为v的物品,这次每个物品可以无限次的取
    for (int i = 1; i <= n; i++) {
      for (int j = 1; j <= m; j++) {
          dp[i][j] = dp[i-1][j];
          //考虑一下要不要放啦
          if(j >= w[i])dp[i][j] = max(dp[i][j],dp[i][j-w[i]]+v[i]);
          //直接在当前的第i个物品处考虑需不需要往下继续放
      }
  }
  1. 多重背包
    每个物品有个数限制
    
    
  2. 二维费用的背包问题
    背包不仅限制重量,还限制体积
  3. 分组背包
    要从每个组别里取一个放入背包的最大值

<1>(「木」迷雾森林)

题解:
从地图左下角走到地图右上角,只能够向上或向右行走,答案对2333取模,1的地方不能走。
一个比较简单的背包dp,不过我会先给出一段错误代码。
代码:
这是一段全部都MLE了的代码

#include<bits/stdc++.h>

using namespace std;
#define int long long
//看这里,long long所占的内存是int的俩倍
//题目给出的空间限制是131072k,相比之下比别的题目小非常多
const int mod = 2333;

int m,n;
int a[3100][3100];
//如果把a改成bool数组也可以卡过,因为bool数组只给每个位置俩种情况
int dp[3100][3100];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> m >> n;
    for (int i = 1; i <= m; i++){
        for (int j = 1; j <= n; j++) {
            cin >> a[i][j];
        }
    }
    dp[m][1] = 1;
    for (int i = m; i >= 1; i--) {
        for (int j = 1; j <= n; j++) {
            if(i == m && j == 1)continue;
            dp[i][j] = (dp[i][j-1] + dp[i+1][j])%mod;
            if(a[i][j] == 1)dp[i][j] = 0;
        }
    }
    cout << dp[1][n]; 
    return 0;
}

这是一段面目全非的ac代码

#include<bits/stdc++.h>

using namespace std;
const int N = 3100;
int a[N][N];
int dp[N][N];
const int mod = 2333;

template<class T>inline void read(T &res)
{
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}

signed main() {
    int m,n;
    read(m);
    read(n);
    for (int i = 1; i <= m; i++){
        for (int j = 1; j <= n; j++) {
            read(a[i][j]);
        }
    }
    dp[m][0] = 1;
    for (int i = m; i >= 1; i--) {
        for (int j = 1; j <= n; j++) {
            if(!a[i][j])dp[i][j] = (dp[i][j-1] + dp[i+1][j])%mod;
        }
    }
    printf("%d",dp[1][n]);
    return 0;
}

<2>(CSL分苹果)

题解:
n个苹果,给出苹果质量是w。需要尽可能均分成俩堆,如无法均分,后者大。
01背包dp的变形。相当于找出不大于res/2的苹果质量最大值。
代码:

#include<bits/stdc++.h>

using namespace std;
#define int long long
const int N = 110;
int n;int w[N];
int dp[N*N];//总质量为i的苹果w所能拿的最大苹果价值总和

signed main() {
   cin >> n;int res = 0;
   for (int i = 1; i <= n; i++) {
       cin >> w[i];res+=w[i];
   }
   for (int i = 1; i <= n; i++) {
       for (int j = res/2; j >= w[i]; j--) {
           dp[j] = max(dp[j],dp[j-w[i]]+w[i]);
       }
   }cout << dp[res/2] << ' ' << res-dp[res/2];
}

<3>(D - Knapsack 1)

一个典型的01dp
题解:
n件物品,每件物品有w的重量和v的价值,有一个可以承载x重量的背包,问它能获得的最大价值。
以下是俩种01背包的代码。
代码:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long
int n,x;
const int N = 100+10;
const int W = 1e5+10;
int w[N],v[N];
int dp[N][W];//前i件物品在j的范围内所能取到的最大价值

void solve() {
  cin >> n >> x;
  for (int i = 1; i <= n; i++) {
      cin >> w[i] >> v[i];
  }
  for (int i = 1; i <= n; i++) {
      for (int j = 1; j <= x; j++) {
          if(j < w[i]) dp[i][j] = dp[i-1][j];
          else dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
      }
  }
  cout << dp[n][x] ;
}

signed main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  int _ = 1;
  //   cin>>_;
  while(_--){
      solve();
  }
  return 0;
}
#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long
int n,x;
const int N = 100+10;
const int W = 1e5+10;
int w[N],v[N];
int dp[W];//空间i所能创造的最大价值

void solve() {
   cin >> n >> x;
   for (int i = 1; i <= n; i++) {
       cin >> w[i] >> v[i];
   }
   for (int i = 1; i <= n; i++) {
       for (int j = x; j >= w[i]; j--) {
           dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
       }
   }
   cout << dp[x];
}

signed main(){
   ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
   int _ = 1;
   //   cin>>_;
   while(_--){
       solve();
   }
   return 0;
}

<4>(E - Knapsack 2)

题解:
在这里插入图片描述
基本题意跟D题相同,但是w及背包容量给到1e9,无法直接开一个1e9的数组,可以考虑将dp开成由体积表示容量的数组,体积最大不超过1e5,具体细节见代码注释。
代码:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long
int n,x;
const int N = 100+10;
const int W = 1e5+10;
int w[N],v[N];
int dp[W];//价值i所需要的最小空间

void solve() {
   cin >> n >> x;
   for (int i = 1; i <= n; i++) {
       cin >> w[i] >> v[i];
   }memset(dp,0x3f3f3f,sizeof dp);
   dp[0] = 0;//需要特别注意

   int res = 0;

   for (int i = 1; i <= n; i++) {
       for (int j = W-1; j >= v[i]; j--) {
           //这里是倒着往前找,正着的话会有重复加的情况
      //     cout << dp[j-v[i]]+w[i] << endl;
           dp[j] = min(dp[j],dp[j-v[i]]+w[i]);
           if(dp[j] <= x) {
               res = max(res,j);
               //所需要的空间不能超过背包空间
  //             cout << i << ' ' << j << "/" << dp[j] << endl;
           }
       }
   }

   cout << res << endl;
}

signed main(){
   ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
   int _ = 1;
   //   cin>>_;
   while(_--){
       solve();
   }
   return 0;
}

3.图论DP / 记忆化搜素

<1>(G - Longest Path)

题解:
给定一个n个点m条边的有向图,题目保证不存在有向环,求最长的路线。
根据拓扑排序先计算出对于每个点的运算顺序,接着一个简单的dp即可。稍微巩固一下拓扑排序。
代码:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long

const int N = 1e5+10;
vector<int>a[N];
vector<int>b[N];
int dp[N],du[N];
int n,m;

queue<int>q;
vector<int>ls;
void tuopo(){
    for (int i = 1; i <= n; i++) {
        if(du[i] == 0) q.push(i);
    }
    while(!q.empty()) {
        int x = q.front();
        q.pop();
        ls.push_back(x);
        for (auto y: a[x]) {
            du[y]--;
            if(du[y] == 0) q.push(y);
        }
    }
};

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int x,y; cin >> x >> y;
        a[x].push_back(y);//x通向哪几条边
        b[y].push_back(x);//有几条边可以到这
        du[y]++;
    }int ans = 0; tuopo();

    for (auto rs: ls) {
        if(b[rs].size() != 0) {
            for (auto ps: b[rs]) {
         //       cout << ps << ' ' << rs << endl;
                dp[rs] = max(dp[rs],dp[ps]+1);
                ans = max(ans,dp[rs]);
            }
        }
    }

    cout << ans << endl;
}

signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int _ = 1;
    //   cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

<2>(H - Grid 1)

题解:
h行w列的二维地图,#位置表示墙不可以走,要求从左上角走到右下角的方案个数,只可以右行或下行。
走到每一个格子的方案数是它的上面与左边一个格子的方案数之和,可以想象从第一排开始自左向右走,可以保证左上俩个格子都已经确认状态。
代码:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long
const int mod = 1e9+7;

int h,w;
const int N = 1100;
char a[N][N];
int b[N][N];

void solve() {
    cin >> h >> w;
    for (int i = 1; i <= h; i++) {
        for (int j = 1; j <= w; j++) {
            cin >> a[i][j];
        }
    }b[1][1] = 1;
    for (int i = 1; i <= h; i++) {
        for (int j = 1; j <= w; j++) {
            if(i == 1 && j == 1) continue;
            b[i][j] = b[i-1][j] + b[i][j-1];
            if(a[i][j] == '#') b[i][j] = 0;
            b[i][j] %= mod;
        }
    }
//    for (int i = 1; i <= h; i++) {
//        for (int j = 1; j <= w; j++) {
//            cout << b[i][j] << ' ';
//        }cout << endl;
//    }
    cout << b[h][w];
}

signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int _ = 1;
    //   cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

4.期望DP

<1>(I - Coins)

题解:
给出n枚硬币正面朝上的概率,求最后正面朝上的硬币个数大于反面朝上的硬币个数的概率。
直接计算到第i个硬币为止,j个硬币正面朝上的概率。要注意i和j分别等于0的情况,需要写清楚前置条件。
代码:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long
#define double long double

int n;
const int N = 3100;
double p[N];
double dp[N][N];

void solve() {
    cin >> n; dp[0][0] = 1;
    for (int i = 1; i <= n; i++) {
        cin >> p[i];
        dp[0][i] = 0;
        dp[i][0] = dp[i-1][0]*(1-p[i]);
    }double ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            dp[i][j] = dp[i-1][j]*(1-p[i])+dp[i-1][j-1]*(p[i]);
         //   cout << dp[i][j] << ' ';
        }//cout << endl;
    }
    for (int i = (n+1)/2; i <= n; i++) ans += dp[n][i];
    printf("%0.10Lf",ans);
}

signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int _ = 1;
    //   cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

<2>(J - Sushi)

一道很好的期望DP
题解:
有n个盘子,每个盘子里有1到3片食物,每次随机选择一个盘子吃掉一个食物,如果是空盘子就不吃,问最后吃掉所有食物的操作次数的期望。
保证了每个盘子里食物的个数只有0123四种情况,可以根据有几个包含123食物的盘子建立dp转移方程式。具体见代码注释。
代码:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>

using namespace std;
#define int long long

const int N = 310;
int n;int a[N];
double dp[N][N][N];
//123食物的盘子个数

void solve() {
    cin >> n; 
    for (int i = 1; i <= n; i++) {
        int x; cin >> x;
        a[x]++;
    }

    for (int k = 0; k <= n; k++) {
        for (int j = 0; j <= n; j++) {
            for (int i = 0; i <= n; i++) {
                if(i == 0 && j == 0 && k == 0) continue;
                //避免没有选到的情况
                if(i) dp[i][j][k] += dp[i-1][j][k]*i/(i+j+k);
                if(j) dp[i][j][k] += dp[i+1][j-1][k]*j/(i+j+k);
                //改变后的状态期望数*概率=改变前的状态期望数
                //不知道为什么,可以考虑概率*概率=概率,然后倒推
                if(k) dp[i][j][k] += dp[i][j+1][k-1]*k/(i+j+k);
                //要注意从三个食物的盘子中拿东西,这个盘子会变成俩个食物
                dp[i][j][k] += (double)(n)/(double)(i+j+k);
                //进行每一步的期望数(也就是排除选择了空盘子的期望)
            }
        }
    }//cout << dp[0][0][1] << endl;
    //必须是kji,注意内部转换式的顺序
    printf("%0.15f",dp[a[1]][a[2]][a[3]]);
}

signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int _ = 1;
    //   cin>>_;
    while(_--){
        solve();
    }
    return 0;
}

3.区间DP

4.数位DP

5.状压DP

  • 去掉最后一位(x>>1)
  • 在最后加一个0(x<<1)
  • 最后一位取反(x xor 1)
  • 把最后一位变成1(x|1)
  • 去掉右起第一个1的左边(x&(-)x)

三、总结

相关文章:

  • 基于linuxC结合epoll + TCP 服务器客户端 + 数据库实现一个注册登录功能
  • 在IDEA中快速注释所有console.log
  • 四种跨模态行人重识别可视化方法
  • Ubuntu22.04搭建freeradius操作说明
  • 实时图像处理:让你的应用更智能
  • 【Bug记录】node-sass安装失败解决方案
  • Thinkphp(TP)框架漏洞攻略
  • Docker 可视化工具 Portainer
  • 【2025】基于springboot+spark的电影推荐系统(源码、万字文档、图文修改、调试答疑)
  • 【Tomcat】部署及优化
  • 阿里云国际站代理商:怎样针对4G/5G网络优化CDN参数?
  • Pyserial库使用
  • 蓝桥杯学习-14子集枚举,二进制枚举
  • 高速电路设计之电源分类及其应用要点
  • netplan是如何操控systemd-networkd的? 笔记250324
  • 深入理解Spring框架:核心概念与组成剖析
  • Android第六次面试总结(okhttp篇)
  • Proteus8打开Proteus7文件(.DSN格式)的方法
  • Ceph集群2025(Squid版)导出高可用NFS集群(下集 )
  • 如何在多个GPU中训练非常大的模型?
  • 德国联邦议院6日下午将举行总理选举第二轮投票
  • 无人机穿越大理崇圣寺千年古塔时“炸机”,当地:肇事者已找到,将被追责
  • 山大齐鲁医院回应护士论文现“男性确诊子宫肌瘤”:给予该护士记过处分、降级处理
  • 申活观察|精致精准精细,城市“双面镜”照见怎样的海派活力
  • 巴菲特批评贸易保护主义:贸易不该被当成武器来使用
  • 民族音乐还能这样玩!这场音乐会由AI作曲