TG可能会用到的动态规划-简易自学
完整校订版见此
以下为未核对不完整版本。
因版权原因,完整精校版不向所有公众开放。
请从您找到本博客的地址查找附带密码(比如简书分享了本网址,请您从简书分享页底部查询密码),感谢您的配合。
这里Pleiades_Antares小可爱qiumi
这几天突然回忆起来以前上的一个课qiumi
然后把当时上课做的笔记简单整理了一下发上来(主要是为了给自己复习orz)
于是就有了这个自学教程(基本上看着这个博客是完全可以学会1551)
希望能够帮助到正在拼命复习NOIP的你辣
没有学过动态规划的戳这个超级简单易懂的动态规划教程!!包教包会!
TG考试可能会用到的动态规划
by Pleiades_Antares
NOIP的例题大概会讲
NOIP2014 飞扬的小鸟
NOIP2017 宝藏
(还有更多的但是
NOIP对动态规划的考察主要在状态和转移方程的设计方面,往往设计出优秀的状态和转移即可在题目中取得不错的分数。
一、树形DP
树形DP用于解决树上问题。
状态常常用f(u,S)来表示已经对子树u这个子问题进行求解,状态为S的值。
转移往往会有子树合并。
如上图所示,可以考虑通过这个我手绘的图片来辅助理解。
其代码常常如下所示:
dfs(u)
initial f(u)
foreach v in son[u]
dfs(v)
update f(u) with f(v)
解释下:
树的直径:
两个画了圈的点的距离即为树的直径。
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)
dp[u][k][color][choice]
t[k][color][choice] void dfs(int u) {
dp[u][][][] = ;
dp[u][][][] = ;
for (int v: son[u]) {
dfs(v);
for (int uk = ; uk <= k; ++uk) {
for (int vk = ; vk <= k; ++vk) {
for (int ucolor = ; ucolor < ; ++ucolor) {
for (int vcolor = ; vcolor < ; ++vcolor) {
for (int uchoice = ; uchoice < ; ++uchoice) {
for (int vchoice = ; vchoice < ; ++vchoice) {
t[uk + vk][u_new_color][uchoice] += dp[u][uk][ucolor][uchoice] * dp[v][vk][vcolor][vchoice]
}
}
}
}
}
}
}
}
//尝试在用u的孩子v的dp数组f(v)更新f(u)的时候,只枚举f(u)
实际上,时间复杂度是O(nk)而非O(nk2)
这就是——01背包实际上。
关于01背包的模板可以参考这篇文章,网上也有很多其他大神有关于01背包的理解,这里就不再赘述。
把std源代码发出来:
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + , maxk = , mod = 1e9 + ; void judge() {
freopen("action.in", "r", stdin);
freopen("action.out", "w", stdout);
return;
} int n, k, siz[maxn], q[maxn], par[maxn], fnt, rar, ans;
int dp[maxn][maxk][][], pd[maxk][][];
vector<int> g[maxn]; inline void Add(int &x, int y) {
x = x + y < mod ? x + y : x + y - mod;
return;
} int main() {
judge();
scanf("%d%d", &n, &k);
for (int i = ; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
q[rar++] = ;
while(fnt != rar) {
int u = q[fnt++];
siz[u] = ;
dp[u][][][] = dp[u][][][] = ;
for (int i = ; i < g[u].size(); ++i) {
int &v = g[u][i];
if(v != par[u]) {
par[q[rar++] = v] = u;
}
}
}
for (int ti = rar - ; ti; --ti) {
int &u = q[ti], &p = par[u], Endu = min(siz[u], k), Endp = min(siz[p], k);
for (int ip = ; ip <= Endp; ++ip) {
for (int xp = ; xp < ; ++xp) {
for (int yp = ; yp < ; ++yp) {
pd[ip][xp][yp] = dp[p][ip][xp][yp];
dp[p][ip][xp][yp] = ;
}
}
}
for (int ip = ; ip <= Endp; ++ip) {
for (int xp = ; xp < ; ++xp) {
for (int yp = ; yp < ; ++yp) {
static int s;
if(s = pd[ip][xp][yp]) {
int End = min(Endu, k - ip);
for (int iu = ; iu <= End; ++iu) {
for (int xu = ; xu < ; ++xu) {
for (int yu = xp ^ ; yu < ; ++yu) {
Add(dp[p][iu + ip][xp][yp | xu], (long long) s * dp[u][iu][xu][yu] % mod);
}
}
}
}
}
}
}
siz[p] += siz[u];
}
for (int xu = ; xu < ; ++xu) {
Add(ans, dp[][k][xu][]);
}
printf("%d\n", (ans + mod) % mod);
return ;
}
二、数位DP
如果题目给定了一个很大的数字,问你有多少符合一系列与
例题:
定义 S(n) 为将 n在 10 进制下的所有数位从小到大排序后得到的数。例如:S(1)=1, S(50394)=3459, S(323) =233
给定 X 求
取模的结果。
数据范围: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
解决状态较为复杂的问题,时间复杂度常常是指数级别。
(qwq我真的列不出来只能放截图了)
S = << n;
for (int s = ; s < S; ++s) {
for (int t = s; ; t = (t - ) & s) {
...
if(t == ) break;
}
}
来一道例题
再来一道例题:
NOIP2014
飞扬的小鸟
思路详解⬇️
NOIP2017 宝藏
先看一下题目:
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 nn 个深埋在地下的宝藏屋,也给出了这 nn个宝藏屋之间可供开发的 mm 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:
这条道路的长度 xx 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <set>
#include <queue>
#include <map>
#include <vector>
#include <utility>
#include <string>
#include <functional> #define rep(i, n) for(int i = 0; i < (n); ++i)
#define forn(i, l, r) for(int i = (l); i <= (r); ++i)
#define per(i, n) for(int i = (n) - 1; i >= 0; --i)
#define nrof(i, r, l) for(int i = (r); i >= (l); --i)
#define SZ(x) ((int)(x).size())
#define ALL(x) (x).begin(), (x).end()
#define mp make_pair
#define pb push_back
#define X first
#define Y second using namespace std; typedef long long LL;
typedef pair<int, int> pii; const int maxn = , maxs = << , oo = 1e9 + ; int n, m, g[maxn][maxn], h[maxn][maxs];
int dp[maxn][maxs], f[maxs][maxs]; void judge() {
freopen("treasure.in", "r", stdin);
freopen("treasure.out", "w", stdout);
return;
} inline bool chkmin(int &x, const int &y) {
return x > y ? x = y, : ;
} int main() {
// judge();
scanf("%d%d", &n, &m);
if(n == ) {
puts("");
return ;
}
rep(i, n) {
rep(j, n) {
g[i][j] = oo;
}
g[i][i] = ;
}
while(m--) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
--u;
--v;
if(w < g[u][v]) {
g[u][v] = g[v][u] = w;
}
}
m = << n;
rep(i, n) {
rep(s, m) {
if((s >> i) & ) {
continue;
}
h[i][s] = oo;
rep(j, n) {
if((s >> j) & ) {
chkmin(h[i][s], g[i][j]);
}
}
}
}
rep(s, m) {
int T = m - ^ s;
for (int t = T; t; t = (t - ) & T) {
rep(i, n) {
if((s >> i) & ) {
if(h[i][t] == oo) {
f[s][t] = oo;
break;
}
f[s][t] += h[i][t];
}
}
}
}
rep(i, n) {
rep(s, m) {
dp[i][s] = oo;
}
}
rep(i, n) {
dp[][ << i] = ;
}
int tmp;
rep(i, n - ) {
rep(s, m) {
if((tmp = dp[i][s]) < oo) {
// printf("dp[%d][%d] = %d\n", i, s, tmp);
int T = m - ^ s;
for (int t = T; t; t = (t - ) & T) {
if(f[t][s] != oo) {
chkmin(dp[i + ][s | t], tmp + f[t][s] * (i + ));
}
}
}
}
}
--m;
int ans = oo;
rep(i, n) {
chkmin(ans, dp[i][m]);
}
printf("%d\n", ans);
return ;
}
有 30000个岛屿从左到右排列,有 n个宝石,在 p1, p2, ..., pn上。 你初始时在 0 号岛上,第一次跳到 d 号岛上,第i (i > 2)次你向右跳跃的距离为第i − 1次跳跃距离 l 或者 l-1 或者l + 1。问最多能拿多少宝石。
数据范围:1 ≤ n, d ≤ 30000。
再看一道题:
TG可能会用到的动态规划-简易自学的更多相关文章
- PJ可能会用到的动态规划选讲-学习笔记
PJ可能会用到的动态规划选讲-学习笔记 by Pleiades_Antares 难度和速度全部都是按照普及组来定的咯 数位状压啥就先不讲了 这里主要提到的都是比较简单的DP 一道思维数学巧题(补昨天) ...
- 在自学java路上遇上的南墙
从2016年12月20号自学java,先是咨询了下培训中心,得小两万,四个月毕业,算了一笔账,一百二十天,合下来每天三百多块,再加上开销之类压力太大,于是开始入坑自学,随后血一般的教训直面而来: 1. ...
- [LeetCode] 系统刷题5_Dynamic Programming
Dynamic Programming 实际上是[LeetCode] 系统刷题4_Binary Tree & Divide and Conquer的基础上,加上记忆化的过程.就是说,如果这个题 ...
- PJ考试可能会用到的数学思维题选讲-自学教程-自学笔记
PJ考试可能会用到的数学思维题选讲 by Pleiades_Antares 是学弟学妹的讲义--然后一部分题目是我弄的一部分来源于洛谷用户@ 普及组的一些数学思维题,所以可能有点菜咯别怪我 OI中的数 ...
- 动态规划TG.lv(1) (洛谷提高历练地)
动态规划TG.lv(1) P1005 矩阵取数游戏 分析:每行不超过80个数字,直接区间DP即可,\(dp[i][j]\)表示区间\([i,j]\)之间取数可以得到的答案,每次向右或者向左扩展即可.但 ...
- Spring框架自学之路——简易入门
目录 目录 介绍 Spring中的IoC操作 IoC入门案例 Spring的bean管理配置文件 Bean实例化的方式 Bean标签的常用属性 属性注入 使用有参构造函数注入属性 使用set方法注入属 ...
- 自学WEB前端到什么程度才能就业
做过多年web前端从业者,回答下这个问题 首先,这个问题主要问:自学web前端技术,如果才能找到一份web前端的工作.按照现在的招聘标准来看,无论你去哪个公司面试,你只需要满足他们公司的需求就可以. ...
- MIT挑战(如何在12个月内自学完成MIT计算机科学的33门课程|内附MIT公开课程资源和学习顺序
译者注:本文译自Scott H. Young的博客,Scott拥有超强的学习能力,曾在12个月内自学完成麻省理工学院计算机科学的33门课程.本文就是他个人对于这次MIT挑战的介绍和总结. 版权声明:本 ...
- 自学Java,需要掌握什么内容才能找到满意的工作?
首先,这个问题主要问:自学Java编程技术,如果才能找到一份Java编程的工作.按照现在的招聘标准来看,无论你去哪个公司面试,你只需要满足他们公司的需求就可以. 找到一份Java编程工作需要掌握的内容 ...
随机推荐
- Java 容器源码分析之ArrayBlockingQueue和LinkedBlockingQueue
Java中的阻塞队列接口BlockingQueue继承自Queue接口. BlockingQueue接口提供了3个添加元素方法. add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会 ...
- jquery根据name属性查找元素
$("div[id]") //选择所有含有id属性的div元素 $("input[name='newsletter']") //选择所有的name属性等于'ne ...
- php 图像裁剪(自定义裁剪图片大小)
<?php /** * 图像裁剪 * @param $title string 原图路径 * @param $content string 需要裁剪的宽 * @param $encode str ...
- AD预测论文研读系列2
EARLY PREDICTION OF ALZHEIMER'S DISEASE DEMENTIA BASED ON BASELINE HIPPOCAMPAL MRI AND 1-YEAR FOLLOW ...
- 一张图读懂PBN飞越转弯衔接TF/CF航段计算
在PBN旁切转弯的基础上,再来看飞越转弯接TF(或CF)航段,保护区结构上有些相似,只是转弯拐角处的保护区边界有“简化”,其余部分是相近的. FlyOver接TF段的标称航迹有一个飞越之后转弯切入航迹 ...
- Python 3 进阶 —— print 打印和输出
在 Python 中,print 可以打印所有变量数据,包括自定义类型. 在 2.x 版本中,print 是个语句,但在 3.x 中却是个内置函数,并且拥有更丰富的功能. 参数选项 可以用 help( ...
- c语言中阶乘的精确值
对于大数的操作,可能超出int,甚至long的表示范围,对此,可以使用数组来存储大数,下列代码为求1000以内数的阶乘的代码,代码如下: #include <stdio.h> #inclu ...
- LeetCode链表相加-Python<二>
上一篇:LeetCode两数之和-Python<一> 题目:https://leetcode-cn.com/problems/add-two-numbers/description/ 给定 ...
- Salesforce数据安全简介
数据安全级别 Salesforce中将数据安全分为若干等级: 组织级别:组织级别的安全设定在整个系统内部都有效.这是最广泛的级别 对象级别:对象级别的安全设定可以限制用户对于对象的权限 字段级别:字段 ...
- 一个Web页面的问题分析
几个月之前我接到一个新的开发任务,要在一个旧的Web页面上面增添一些新的功能.在开发的过程中发现旧的代码中有很多常见的不合适的写法,结合这些问题,如何写出更好的,更规范的,更可维护的代码,就是这篇文章 ...