SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

最新更新

完整校订版见此

戳我阅读

以下为未核对不完整版本。

因版权原因,完整精校版不向所有公众开放。

请从您找到本博客的地址查找附带密码(比如简书分享了本网址,请您从简书分享页底部查询密码),感谢您的配合。

 

 

这里Pleiades_Antares小可爱qiumi

这几天突然回忆起来以前上的一个课qiumi

然后把当时上课做的笔记简单整理了一下发上来(主要是为了给自己复习orz)

于是就有了这个自学教程(基本上看着这个博客是完全可以学会1551)

希望能够帮助到正在拼命复习NOIP的你辣

 

没有学过动态规划的戳这个超级简单易懂的动态规划教程!!包教包会!

TG考试可能会用到的动态规划

by Pleiades_Antares

 

NOIP的例题大概会讲

NOIP2014 飞扬的小鸟

NOIP2017 宝藏

(还有更多的但是

 

NOIP对动态规划的考察主要在状态和转移方程的设计方面,往往设计出优秀的状态和转移即可在题目中取得不错的分数。

 

一、树形DP

树形DP用于解决树上问题。

状态常常用f(u,S)来表示已经对子树u这个子问题进行求解,状态为S的值。

转移往往会有子树合并。

TG可能会用到的动态规划-简易自学 算法 第1张

如上图所示,可以考虑通过这个我手绘的图片来辅助理解。

TG可能会用到的动态规划-简易自学 算法 第2张

 

其代码常常如下所示:

1 dfs(u)
2     initial f(u)
3     foreach v in son[u]
4         dfs(v)
5         update f(u) with f(v)    

 解释下:

树的直径:

TG可能会用到的动态规划-简易自学 算法 第3张

两个画了圈的点的距离即为树的直径。

 

f(u)表示以u为根的子树,到u的最长链的长度

来一道例题热身:

有一颗n个节点n-1条边的无向树,树上节点用1,2,.....n编号。

初始时每个节点都是白色。如果选中了节点u,则对于树中的每条边(u,v),v都会被染成黑色。注意u自身不会被染黑。

现在总共要选择恰好k个点,问有多少种方法使得所有节点被染黑。

数据范围:1<=n<=100000,1<=k<=min(n,100)

先考虑n<=1000时要怎么做?

令dp(u,k,color,choice)表示对于以u为根的子树,里面选了k个点,u的颜色为黑/白,u有没有被选中。u子树外点的选择只能影响到u子树中u点的颜色,

u子树只有u点的选择情况能影响到u子树外的点的颜色。所以这样的状态时足够的。

 

 

时间复杂度:O(nk2

 1 dp[u][k][color][choice]
 2 t[k][color][choice]
 3 
 4 void dfs(int u) {
 5     dp[u][1][0][1] = 1;
 6     dp[u][0][0][0] = 1;
 7     for (int v: son[u]) {
 8         dfs(v);
 9         for (int uk = 0; uk <= k; ++uk) {
10             for (int vk = 0; vk <= k; ++vk) {
11                 for (int ucolor = 0; ucolor < 2; ++ucolor) {
12                     for (int vcolor = 0; vcolor < 2; ++vcolor) {
13                         for (int uchoice = 0; uchoice < 2; ++uchoice) {
14                             for (int vchoice = 0; vchoice < 2; ++vchoice) {
15                                 t[uk + vk][u_new_color][uchoice] += dp[u][uk][ucolor][uchoice] * dp[v][vk][vcolor][vchoice]
16                             }
17                         }
18                     }
19                 }
20             }
21         }
22     }
23 }

 

//尝试在用u的孩子v的dp数组f(v)更新f(u)的时候,只枚举f(u)

实际上,时间复杂度是O(nk)而非O(nk2

这就是——01背包实际上。

关于01背包的模板可以参考这篇文章,网上也有很多其他大神有关于01背包的理解,这里就不再赘述。

 

把std源代码发出来:

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 const int maxn = 1e5 + 10, maxk = 110, mod = 1e9 + 7;
 6 
 7 void judge() {
 8     freopen("action.in", "r", stdin);
 9     freopen("action.out", "w", stdout);
10     return;
11 }
12 
13 int n, k, siz[maxn], q[maxn], par[maxn], fnt, rar, ans;
14 int dp[maxn][maxk][2][2], pd[maxk][2][2];
15 vector<int> g[maxn];
16 
17 inline void Add(int &x, int y) {
18     x = x + y < mod ? x + y : x + y - mod;
19     return;
20 }
21 
22 int main() {
23     judge();
24     scanf("%d%d", &n, &k);
25     for (int i = 1; i < n; ++i) {
26         int u, v;
27         scanf("%d%d", &u, &v);
28         g[u].push_back(v);
29         g[v].push_back(u);
30     }
31     q[rar++] = 1;
32     while(fnt != rar) {
33         int u = q[fnt++];
34         siz[u] = 1;
35         dp[u][0][0][0] = dp[u][1][1][0] = 1;
36         for (int i = 0; i < g[u].size(); ++i) {
37             int &v = g[u][i];
38             if(v != par[u]) {
39                 par[q[rar++] = v] = u;
40             }
41         }
42     }
43     for (int ti = rar - 1; ti; --ti) {
44         int &u = q[ti], &p = par[u], Endu = min(siz[u], k), Endp = min(siz[p], k);
45         for (int ip = 0; ip <= Endp; ++ip) {
46             for (int xp = 0; xp < 2; ++xp) {
47                 for (int yp = 0; yp < 2; ++yp) {
48                     pd[ip][xp][yp] = dp[p][ip][xp][yp];
49                     dp[p][ip][xp][yp] = 0;
50                 }
51             }
52         }
53         for (int ip = 0; ip <= Endp; ++ip) {
54             for (int xp = 0; xp < 2; ++xp) {
55                 for (int yp = 0; yp < 2; ++yp) {
56                     static int s;
57                     if(s = pd[ip][xp][yp]) {
58                         int End = min(Endu, k - ip);
59                         for (int iu = 0; iu <= End; ++iu) {
60                             for (int xu = 0; xu < 2; ++xu) {
61                                 for (int yu = xp ^ 1; yu < 2; ++yu) {
62                                     Add(dp[p][iu + ip][xp][yp | xu], (long long) s * dp[u][iu][xu][yu] % mod);
63                                 }
64                             }
65                         }
66                     }
67                 }
68             }
69         }
70         siz[p] += siz[u];
71     }
72     for (int xu = 0; xu < 2; ++xu) {
73         Add(ans, dp[1][k][xu][1]);
74     }
75     printf("%d\n", (ans + mod) % mod);
76     return 0;
77 }

 

二、数位DP

如果题目给定了一个很大的数字,问你有多少符合一系列与

例题:

定义 S(n) 为将 n在 10 进制下的所有数位从小到大排序后得到的数。例如:S(1)=1, S(50394)=3459, S(323) =233

给定 X 求TG可能会用到的动态规划-简易自学 算法 第4张取模的结果。 

数据范围:1<=X<=10700.

 

考虑如何计算答案,可以通过分别计算每一位对总和的贡献来求。定义 cnt(i, x)为 S(1),S(2),...,S(X)中第i位为x 的数量。

直接求 cnt(i, x)仍然较为困难,考虑差分。令dlt(i, x) 为第 i 位上有多少个数  x。对于一个 S(y) 中第 i 位  x 的数 y,可以发现其必须满足有至少 i 个数位  x,这样就可以进行dp了。

另 dp(i, j, x, cmp) 表示填了前 i位,有至少 j个数字 x,与 N 的大小关系位cmp。

转移时直接枚举第i + 1 位数字是多少即可。

 

三、状压DP

解决状态较为复杂的问题,时间复杂度常常是指数级别。

TG可能会用到的动态规划-简易自学 算法 第5张

(qwq我真的列不出来只能放截图了)

1 S = 1 << n;
2 for (int s = 0; s < S; ++s) {
3     for (int t = s; ; t = (t - 1) & s) {
4     ...
5     if(t == 0) break;
6     }
7 }

来一道例题

TG可能会用到的动态规划-简易自学 算法 第6张

 

 

再来一道例题:

NOIP2014

 

飞扬的小鸟

 

思路详解⬇️

TG可能会用到的动态规划-简易自学 算法 第7张

 

 

 

NOIP2017 宝藏

先看一下题目:

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 nn 个深埋在地下的宝藏屋,也给出了这 nn个宝藏屋之间可供开发的 mm 条道路和它们的长度。

小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。

小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。

在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。

新开发一条道路的代价是:

这条道路的长度 xx 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。

请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。

 这道题有很多种做法(雾) 放一个正确的代码(DP)
  1 #include <cstdio>
  2 #include <cstdlib>
  3 #include <cstring>
  4 #include <cmath>
  5 #include <iostream>
  6 #include <algorithm>
  7 #include <set>
  8 #include <queue>
  9 #include <map>
 10 #include <vector>
 11 #include <utility>
 12 #include <string>
 13 #include <functional>
 14 
 15 #define rep(i, n) for(int i = 0; i < (n); ++i)
 16 #define forn(i, l, r) for(int i = (l); i <= (r); ++i)
 17 #define per(i, n) for(int i = (n) - 1; i >= 0; --i)
 18 #define nrof(i, r, l) for(int i = (r); i >= (l); --i)
 19 #define SZ(x) ((int)(x).size())
 20 #define ALL(x) (x).begin(), (x).end()
 21 #define mp make_pair
 22 #define pb push_back
 23 #define X first
 24 #define Y second
 25 
 26 using namespace std;
 27 
 28 typedef long long LL;
 29 typedef pair<int, int> pii;
 30 
 31 const int maxn = 12, maxs = 1 << 12, oo = 1e9 + 7;
 32 
 33 int n, m, g[maxn][maxn], h[maxn][maxs];
 34 int dp[maxn][maxs], f[maxs][maxs];
 35 
 36 void judge() {
 37     freopen("treasure.in", "r", stdin);
 38     freopen("treasure.out", "w", stdout);
 39     return;
 40 }
 41 
 42 inline bool chkmin(int &x, const int &y) {
 43     return x > y ? x = y, 1 : 0;
 44 }
 45 
 46 int main() {
 47 //    judge();
 48     scanf("%d%d", &n, &m);
 49     if(n == 1) {
 50         puts("0");
 51         return 0;
 52     }
 53     rep(i, n) {
 54         rep(j, n) {
 55             g[i][j] = oo;
 56         }
 57         g[i][i] = 0;
 58     }
 59     while(m--) {
 60         int u, v, w;
 61         scanf("%d%d%d", &u, &v, &w);
 62         --u;
 63         --v;
 64         if(w < g[u][v]) {
 65             g[u][v] = g[v][u] = w;
 66         }
 67     }
 68     m = 1 << n;
 69     rep(i, n) {
 70         rep(s, m) {
 71             if((s >> i) & 1) {
 72                 continue;
 73             }
 74             h[i][s] = oo;
 75             rep(j, n) {
 76                 if((s >> j) & 1) {
 77                     chkmin(h[i][s], g[i][j]);
 78                 }
 79             }
 80         }
 81     }
 82     rep(s, m) {
 83         int T = m - 1 ^ s;
 84         for (int t = T; t; t = (t - 1) & T) {
 85             rep(i, n) {
 86                 if((s >> i) & 1) {
 87                     if(h[i][t] == oo) {
 88                         f[s][t] = oo;
 89                         break;
 90                     }
 91                     f[s][t] += h[i][t];
 92                 }
 93             }
 94         }
 95     }
 96     rep(i, n) {
 97         rep(s, m) {
 98             dp[i][s] = oo;
 99         }
100     }
101     rep(i, n) {
102         dp[0][1 << i] = 0;
103     }
104     int tmp;
105     rep(i, n - 1) {
106         rep(s, m) {
107             if((tmp = dp[i][s]) < oo) {
108 //                printf("dp[%d][%d] = %d\n", i, s, tmp);
109                 int T = m - 1 ^ s;
110                 for (int t = T; t; t = (t - 1) & T) {
111                     if(f[t][s] != oo) {
112                         chkmin(dp[i + 1][s | t], tmp + f[t][s] * (i + 1));
113                     }
114                 }
115             }
116         }
117     }
118     --m;
119     int ans = oo;
120     rep(i, n) {
121         chkmin(ans, dp[i][m]);
122     }
123     printf("%d\n", ans);
124     return 0;
125 }

 

 

 

 

有 30000个岛屿从左到右排列,有 n个宝石,在 p1, p2, ..., pn上。 你初始时在 0 号岛上,第一次跳到 d 号岛上,第i (i > 2)次你向右跳跃的距离为第i  1次跳跃距离 l 或者 l-1 或者l + 1。问最多能拿多少宝石。

数据范围:1  n, d  30000。

再看一道题:

TG可能会用到的动态规划-简易自学 算法 第8张

TG可能会用到的动态规划-简易自学 算法 第9张

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄