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编程工作需要掌握的内容 ...
随机推荐
- PHP-CPP开发扩展(六)
PHP-CPP是一个用于开发PHP扩展的C++库.本节讲解在C++中PHP异常.变量.常量的实现相关知识. 异常 PHP和C++都支持异常,而PHP-CPP库这两种语言之间的异常处理是完全透明的.你在 ...
- NIO ServerSocketChannel ScoketChannel
package com.yb.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.S ...
- Electron学习(一)——— electron的安装
前言 本人是做java开发的(菜鸟),做web项目的朋友们基本上都会遇到同样一个,永远不知道客户会怎么样使用,或者说永远不知道客户会用什么浏览器打开我们做出来的应用,就算你跟他说明了一定得用某某某浏览 ...
- MySQL中的事务及读写锁实现并发访问控制
一.并发控制中锁的概念 锁是并发控制中最核心的概念之一,在MySQL中的锁分两大类,一种是读锁,一种是写锁,读锁也可以称为共享锁(shared lock),写锁也通常称为排它锁(exclusive l ...
- java的四个基本特征
现实生活中的事物被抽象成对象,把具有相同属性和行为的对象被抽象成类,再从具有相同属性和行为的类中抽象出父类. 封装 隐藏对象的属性和实现细节,仅仅对外公开接口. 封装的有优点: 1.便于使用者正确.方 ...
- 自定义滚动条mCustomScrollbar
mCustomScrollbar 是个基于 jQuery UI 的自定义滚动条插件,它可以让你灵活的通过 CSS 定义网页的滚动条,并且垂直和水平两个方向的滚动条都可以定义,它通过 Brandon A ...
- C#关闭子窗口而不释放子窗口对象的问题解决
在网上找来一些方式,感觉还都不错,下面给出方式: 在线扫描相机的调试过程中,需要开辟调试界面来进行位置的配置.调试结束后,一种常用的方式是将调试参数保存并在下次启动时加载.另一种简单方式是直接使用该参 ...
- MVC中返回json数据的两种方式
MVC里面如果直接将数据返回到前端页面,我们常用的方式就是用return view(): 那么我不想直接用razor语法,毕竟razor这玩意儿实在是太难记了,还不如写ajax对接来得舒服不是 那么我 ...
- 微信服务号 redirect_uri域名与后台配置不一致,错误代码10003
微信服务号开发获取用户openid时一直提示 redirect_uri域名与后台配置不一致,错误代码10003:后台也配置了域名 原因: 结果:获取到了openid
- vue项目导入外部css样式和js文件
<template> <div id="app" > </div> </template> <script src=" ...