dp的进阶 (一)
熟练掌握dp的定义方法。
①四维dp的转移,生命值转移时候需要注意的
②集合的定义,判断二进制内部是否有环
③很难想到的背包问题
④博弈类型的dp
⑤排列组合类型dp
⑥01背包的变种(01背包+完全背包)
⑦codeforces Good bye 2016 E 线段树维护dp区间合并
⑧
⑨
⑩
⑪
⑫
⑬
⑭
⑮
⑯
⑰
⑱
⑲
⑳
一:http://www.cnblogs.com/heimao5027/p/5988770.html
四维dp 或者 剪枝 + dfs Codeforces Beta Round #6 (Div. 2 Only) D
定义方法:
定义dp[i][j][k][z]表示目前攻击第i个人,j表示第i-1个人的生命,k表示第i个人的生命,z表示第i+1个人的生命。然后转移需要注意一下以下方面
①简单的转移:dp[i][j - b][k - a][z - b] = dp[i][j][k][z];
②如果j-b以后的生命值为0了,那么dp[i+1][k - a][z - b][h[i + 2]]=dp[i][j][k][z] + 1;
③如果j本来就为0,那么dp[i+1][k][z][h[i + 2]] = dp[i][j][k][z];
学习转移:如果其中一个的生命值为0,可以往下一个位置进行转移
二:http://codeforces.com/problemset/problem/11/D
状压dp判断连通环的个数
题目大意:给你n个点,m条边,找该图中有几个换
思路:定义dp[i][j]表示i是圈的集合,j表示该集合的终点,定义起点为这些走过的点里面最小的。然后dp就表示为i集合中所有的环的种类数
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
const int maxn = ;
LL dp[ << maxn][maxn + ];
int a[maxn + ][maxn + ];
int n, m; void get_min(int val, int &cnt, int &st){
int pos = ;
while (val){
if (val & ) st = min(st, pos), cnt++;
val >>= ;
pos++;
}
}
///定义dp[i][j]表示i是圈的集合,j表示该集合的终点,定义起点为这些走过的点里面最小的
int main(){
cin >> n >> m;
for (int i = ; i <= m; i++){
int u, v; scanf("%d%d", &u, &v);
u--, v--;
a[u][v] = a[v][u] = ;
}
LL ans = ;
for (int i = ; i < n; i++) dp[ << i][i] = ;///初始化
for (int i = ; i < ( << n); i++){
int cnt = , st = n;
get_min(i, cnt, st);
for (int j = ; j < n; j++){
if (dp[i][j]){///目前集合为i,终点为j。而且根据放入集合的顺序,j必然是在i集合里面的
if (a[st][j] && cnt >= ){///放入集合的最终的终点一定得是在st的旁边。否则会将形不成环的放入ans中
ans += dp[i][j];
}
for (int k = st + ; k < n; k++){///放入终点大于st的。根据定义st必然为最小的起点。
///然后k<st的已经在更小的状态中全部都计算过了
if (a[j][k] && !(i & (1 << k))){
dp[i | (1 << k)][k] += dp[i][j];
}
}
}
}
}
/*haha;
for(int i = 0; i < n; i++){
for (int j = 0; j < (1 << n); j++){
printf("%lld ", dp[j][i]);
}
cout << endl;
}*/
cout << ans / << endl;
return ;
}
学习:集合的定义、终点的定义的学习
当然,看到有位大牛使用记忆化写的。
/*************
看了大神的博客才有所感悟啊,记忆化搜索+状态压缩。。。。太神了...
这种复杂度,近百万的DFS复杂度居然没有TLE,果然经验不足,菜鸟一只。
用状态压缩枚举起点和可能经过的点。
可以判定的简单通路 i->j,存在的条数为sum(i->k) 其中k,j之间有边。
当然,每次计算通路个数的时候,可以借每一个k来判断回路的条数,当然只能算一次。
这样加出来的回路会有重复,因为可能把顺逆两种方向运动的回路都考虑进去。
记忆化搜索的好处是可以精确的计算每次遍历点的情况,而如果单纯只是循环的话,却没有这么
灵活。
****************/
#define LL long long
#include<cstdio>
#include<cstring>
#define haha printf("haha\n")
const int LMT=,LMS=<<;
LL dp[LMS][LMT],ans;
int n,start,tem,gra[LMT][LMT]; int get_one(int x){
int res=;
do
res += x&;
while(x >>= );
return res;
} int left(int x){
int pos = ;
while (x){
if (x & ) return pos;
x >>= ; pos++;
}
return pos;
} LL dfs(int mas,int end){
if(dp[mas][end] >= )return dp[mas][end];
LL res = ;
for(int j = start; j < n; j++){
if(gra[j][end] && (( << j) & mas) && (tem == || j != start)){
///if (tem == 2 && j == start) haha;
/**j是可以等于start的,当tem==2,因为这个时候集合里面就只有两个元素了
所以只有在这样的情况下,j才能等于tem**/
tem--;
res+=dfs(mas^(<<end),j);
tem++;
}
}
if(tem > && gra[end][start]){
ans += res;
}
dp[mas][end] = res;
return res;
} int main(){
int m;
scanf("%d%d",&n,&m);
memset(dp,-,sizeof(dp));
while(m--){
int u,v;
scanf("%d%d",&u,&v);
u--;v--;
gra[u][v] = gra[v][u] = ;
}
for(int i = ;i < n; i++) dp[ << i][i] = ;
for(int t = ; t < ( << n); t++){
start = left(t);///以集合中最小的为起点
tem = get_one(t);///有几个1
for(int j = start + ; j < n && tem > ; j++){
if(gra[j][start] && ((<<j) & t)){///以集合中最小的周围的两个为终点
dfs(t,j);
}
}
}
printf("%I64d\n",ans / );
return ;
}
三:http://codeforces.com/problemset/problem/730/J
题目大意:你有n个杯子,每个杯子里面有a[i]升水,每个杯子的容量为b[i]。问,用最少的杯子来装这些水,在该前提下,倒水所用的时间最短(倒1L水需要1秒)
思路:我的心路历程很复杂= =。dp一直定义不对,起初是(balabal,还是写在代码中了,这里就不提了)
然后说一下正确的,定义dp(i,j)表示i个水杯,里面有jL水的最大容量。然后转移就好了。
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
const int maxn = + ;
int n;
/*
定义dp(i, j)
假设当前使用i个杯子装水,可装容量为j,所需要的最短时间 定义dp(i, j, f),表示i~j区间内装f个杯子,所需要的最少时间 定义dp(i, j)
当前为第i个杯子,可用容量为j,所需要的最少的杯子个数
如果杯子个数一样,那么就记录下时间
并且维护一下前一个该区间的可用容量 定义dp(i, j)
用i个杯子,目前i个杯子里面的含水量为j的容量最大的值
*/
int b[maxn];
int dp[maxn][maxn * maxn];
pair<int, int> a[maxn]; int main(){
scanf("%d", &n);
int watersum = ;
for (int i = ; i <= n; i++){///已有体积
scanf("%d", &a[i].fi);
watersum += a[i].fi;
}
for (int i = ; i <= n; i++){///容量
scanf("%d", &a[i].se);
b[i] = a[i].se;
}
sort(b + , b + + n);
int cnt = , sum = ;
for (int i = n; i > ; i--){
sum += b[i];
cnt++;
if (sum >= watersum) break;
}
memset(dp, -, sizeof(dp));
dp[][] = ;
for (int i = ; i <= n; i++){
for (int j = sum; j >= a[i].fi; j--){
for (int k = i; k >= ; k--){
if (dp[k - ][j - a[i].fi] != -){
dp[k][j] = max(dp[k][j], dp[k - ][j - a[i].fi] + a[i].se);
}
}
}
}
int ans = 0x3f3f3f3f;
for (int i = sum; i >= ; i--){
if (dp[cnt][i] != - && dp[cnt][i] >= watersum){
ans = min(ans, watersum - i);
}
}
printf("%d %d\n", cnt, ans);
return ;
}
学习:讲道理这道题我一直想着正向推,结果思维定式了。这题应该反过来定义最大容量然后逆向推的,我还是太菜了
四:Codeforces Round #376 (Div. 2) E
题目大意:每次都选左边k(2<=k<=m)个合并,然后那个人得到合并以后的val,并且将这个val放在最左边。问两个人都采取最优的方法。问差值最大的几?
思路一:dfs的方法
因为每次都至少取两个,且每次取过以后就合并,所以不难想到要维护一下前缀和。然后就是我们发现,每次取的时候要么是某个人接着取,要么就是那个人不取了,换成另外一个人取,所以每个位置会有两种决策,但是前缀和的维护还是不变的。所以我们用dfs可以很简单的时间这种思路。复杂度O(n)
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
/*
题目大意:
每次都选左边k(2<=k<=m)个合并,然后那个人得到合并以后的val,并且将这个val放在最左边
问两个人都采取最优的方法。问差值最大的几?
*/
const int maxn = + ;
LL sum[maxn];
int n; LL dfs(int pos){
if (pos == n) return sum[n];
LL val = dfs(pos + );
return max(val, sum[pos] - val);
} int main(){
scanf("%d", &n);
for (int i = ; i <= n; i++){
LL val; scanf("%lld", &val);
sum[i] = sum[i - ] + val;
}
printf("%lld\n", dfs());
return ;
}
关键:要明白每一次取的val是要么一个人接着取,要么就换一个人。我之所以卡住了是因为每次dp的时候都想要把所有的都合并起来,所以没有想到这里
五:Codeforces Round #187 (Div. 1) C
题目大意:给你一个len=n的数字串,你需要找出他所有的非空非严格递增的子序列。然后把所有子序列的和输出即可。
例如样例二的解释为:给你一个1 2 2的串,他的子串有:{1}{2}{1,2}{2,2}{1,2,2}
思路:orz,以前没想到自己竟然做过一遍,然后做过一遍以后现在再做又不会了,果然是没有定时复习的缘故吧?
定义dp(i)表示目前以数字i为结尾的所有子串的val和,那么dp[K] =( dp[0] + dp[1] + dp[2] +... + dp[K] ) * K + K; 所以要用到个东东动态统计前缀和
举例:1 2 3
第一步:{1}
第二步:{1,2} {2}
第三步:{1,2,3}{2,3}{3}
所以dp(k)的转移方程就很容易可以写出来了
#include<bits/stdc++.h> using namespace std; typedef long long ll;
const int mod = 1e9 + ;
const int maxa = + ;
int n;
ll a[maxa];
ll b[maxa];
ll tree[maxa << ]; ll sum(ll x){
ll res = ;
while (x){
res = (res + tree[x]) % mod;
x -= x & -x;
}
return res % mod;
} void add(ll x, ll val){
while (x <= maxa){
tree[x] = (tree[x] + val) % mod;
x += x & -x;
}
} int main(){
scanf("%d", &n);
ll res = ;
for (int i = ; i <= n; i++){
ll t;
scanf("%lld", &t);
ll tmp = sum(t);
tmp = (tmp * t + t) % mod;
res = (res + tmp - a[t] + mod) % mod;
add(t, tmp - a[t]);
a[t] = tmp;
}
printf("%lld\n", res);
return ;
}
关键:题目一定要读懂,明白方式搭配
六:HDU 3033
题目大意:题意:有S款运动鞋,一个n件,总钱数为m,求不超过总钱数且每款鞋子至少买一双的情况下,使价值最大。如果 有一款买不到,就输出“Impossible"。
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
/*
题目大意:有n个鞋子,有m元,k类鞋子
输入n行,每行表示种类,价格,和val
并且每种鞋子都至少选一个
*/
const int maxn = + ;
vector<pair<int, LL> > ve[];
int n, m, k;
LL dp[][maxn];
/*
定义dp(i, j)表示目前是第i种商品,还剩下j元
*/
LL solve(){
memset(dp, -, sizeof(dp));
LL ans = -;
for (int j = ; j <= m; j++) dp[][j] = ;
for (int i = ; i <= k; i++){
for (int f = ; f < ve[i].size(); f++){
for (int j = m; j >= ve[i][f].fi; j--){
//if (dp[i - 1][j - ve[i][f].fi] == -1) continue;
dp[i][j] = max(dp[i - ][j - ve[i][f].fi] + ve[i][f].se, max(dp[i][j], dp[i][j - ve[i][f].fi] + ve[i][f].se));
}
}
}
for (int i = ; i <= m; i++){
ans = max(ans, dp[k][i]);
}
return ans;
} int main(){
while (scanf("%d%d%d", &n, &m, &k) == ){
for (int i = ; i <= k; i++) ve[i].clear();
for (int i = ; i <= n; i++){
int a, b, c; scanf("%d%d%d", &a, &b, &c);
if (b > m) continue;
ve[a].push_back(mk(b, 1LL * c));
}
LL val = solve();
if (val >= ) printf("%lld\n", val);
else puts("Impossible");
}
return ;
}
/*
5 50 3
1 10 20
1 15 30
3 10 60
3 15 30
2 10 10
ans : 130
*/
关键:既要掌握01背包的dp,又要掌握完全背包的dp,
七:链接:这里
codeforces Good bye 2016 E 线段树维护dp区间合并
题目大意:给你一个字符串,范围为‘0’~'9',定义一个ugly的串,即串中的子串不能有2016,但是一定要有2017,问,最少删除多少个字符,使得串中符合ugly串?
思路:定义dp(i, j),其中i=5,j=5,因为只需要删除2016当中其中一个即可,所以一共所需要删除的字符和需要的字符为20176,因此i和j只要5就够了。
然后转移就是dp(i,i) = 0, 如果说区间大小为1的话,那么如果是2017中的一个,那么就是dp(pos, pos+1) = 0, dp(pos,pos) = 1。但是如果pos为'6'这个字符,那么定义6这个pos=x转移就要为dp[x-1][x-1] = dp[x][x] = 1.然后我们再去转移就好了。
然后最后一定要注意线段树的合并顺序哦
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
/*
定义dp(x,i,j)表示区间[0,x)内能够获得的2017的前缀长度为i的,至少要删除多少个字符
最少要删除多少个才能构成前缀为j的2017,然后我们不需要去管x,只需要用线段树去维护i,j即可。
*/
const int inf = 0x3f3f3f3f;
const int maxn = + ;
struct Node{
int dp[][];
}tree[maxn << ];
int n, q;
char ch[maxn];
map<char, int> id; inline void init(Node &t){
for (int i = ; i < ; i++)
for (int j = ; j < ; j++)
t.dp[i][j] = inf;
} Node Merge(Node a, Node b){
Node tmp; init(tmp);
for (int i = ; i <= ; i++)
for (int j = i; j <= ; j++)
for (int k = i; k <= j; k++)
tmp.dp[i][j] = min(tmp.dp[i][j], a.dp[i][k] + b.dp[k][j]);
return tmp;
} void display(Node t){
for (int i = ; i < ; i++){
for (int j = ; j < ; j++)
printf("%d ", t.dp[i][j]);
cout << endl;
}
} void buildtree(int l, int r, int o){
if (l == r){
init(tree[o]);
for (int i = ; i < ; i++)
tree[o].dp[i][i] = ;
int pos = id[ch[l]];
if (pos <= ) tree[o].dp[pos][pos] = , tree[o].dp[pos][pos + ] = ;
if (pos == ) tree[o].dp[][] = tree[o].dp[][] = ;
return ;
}
int mid = (l + r) / ;
if (l <= mid) buildtree(l, mid, o << );
if (r > mid) buildtree(mid + , r, o << | );
tree[o] = Merge(tree[o << ], tree[o << | ]);
//display(tree[o]);
} Node ans;
void query(int ql, int qr, int l, int r, int o){
if (ql <= l && qr >= r) {
if (ql == l) ans = tree[o];
else ans = Merge(ans, tree[o]);
return ;
}
int mid = (l + r) / ;
if (ql <= mid) query(ql, qr, l, mid, o << );
if (qr > mid) query(ql, qr, mid + , r, o << | );
} int main(){
int cnt = ;
id[''] = , id[''] = , id[''] = , id[''] = , id[''] = ;
for (char i = ''; i <= ''; i++) if(id.count(i) == ) id[i] = ++cnt;
cin >> n >> q;
scanf("%s", ch + );
buildtree(, n, );
for (int i = ; i <= q; i++){
int ql, qr; scanf("%d%d", &ql, &qr);
init(ans);
query(ql, qr, , n, );
if (ans.dp[][] >= inf) puts("-1");
else printf("%d\n", ans.dp[][]);
}
return ;
}
关键:线段树合并
八:
九:
十:
十一:
十二:
十三:
dp的进阶 (一)的更多相关文章
- 树形dp的进阶 (一)
①树的重心的性质的运用 ②缩点以后寻找规律 树的直径! ③树形dp上的公式转换 ④和期望有关的树形dp + 一点排列组合的知识 ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ 一:Codeforces Round #364 ...
- hdu 4117 GRE Words AC自动机DP
题目:给出n个串,问最多能够选出多少个串,使得前面串是后面串的子串(按照输入顺序) 分析: 其实这题是这题SPOJ 7758. Growing Strings AC自动机DP的进阶版本,主题思想差不多 ...
- 【[APIO/CTSC2007]动物园】状压DP
题目测评:https://www.luogu.org/problemnew/show/P3622 题目描述 新建的圆形动物园是亚太地区的骄傲.圆形动物园坐落于太平洋的一个小岛上,包含一大圈围栏,每个围 ...
- 洛谷P3387 【模板】缩点 题解
背景 今天\(loj\)挂了,于是就有了闲情雅致来刷\(luogu\) 题面 洛谷P3387 [模板]缩点传送门 题意 给定一个\(n\)个点\(m\)条边有向图,每个点有一个权值,求一条路径,使路径 ...
- [Leetcode] Combination Sum 系列
Combination Sum 系列题解 题目来源:https://leetcode.com/problems/combination-sum/description/ Description Giv ...
- 华东交通大学 2019 I 不要666 数位dp进阶
Problem Description 题库链接 666是一个网络用语,用来形容某人或某物很厉害很牛.而在西方,666指魔鬼,撒旦和灵魂,是不吉利的象征.所以邓志聪并不喜欢任何与6有关的数字.什么数字 ...
- 数位dp进阶(hdu2089,3652)
之前的文章已经讲过如何求1—r中的特殊数,这篇博客就来讲些进阶操作: 直接看例题(hdu2089): (题目是中文的我就不写大意了) 这题与hdu3555最大的区别就是规定了l,不再以1开始: 解决这 ...
- ACM/ICPC 之 DP进阶(51Nod-1371(填数字))
原题链接:填数字 顺便推荐一下,偶然看到这个OJ,发现社区运营做得很赞,而且交互和编译环境都很赞(可以编译包括Python,Ruby,Js在内的脚本语言,也可以编译新标准的C/C++11,甚至包括Go ...
- DP——由蒟蒻到神犇的进阶之路
开始更新咯 DP专题[题目来源BZOJ] 一.树形DP 1.bzoj2286消耗战 题解:因为是树形结构,一个点与根节点不联通,删一条边即可, 于是我们就可以简化这棵树,把有用的信息建立一颗虚树,然后 ...
随机推荐
- BugkuCTF web2
前言 写了这么久的web题,算是把它基础部分都刷完了一遍,以下的几天将持续更新BugkuCTF WEB部分的题解,为了不影响阅读,所以每道题的题解都以单独一篇文章的形式发表,感谢大家一直以来的支持和理 ...
- 关于java线程池的一丢丢
线程池应用达到的目的 1.降低资源消耗:可以重复利用已创建的线程从而降低线程创建和销毁所带来的消耗. 2.提高响应速度:当任务到达时,不需要等线程创建就可以立即执行. 3.提高线程的可管理性:使用线程 ...
- vue项目eslint配置 以及 解释
// https://eslint.org/docs/user-guide/configuring module.exports = { root: true, parserOptions: { pa ...
- CSAPP lab2 二进制拆弹 binary bombs phase_6
给出对应于7个阶段的7篇博客 phase_1 https://www.cnblogs.com/wkfvawl/p/10632044.htmlphase_2 https://www.cnblogs. ...
- Linux第二章读书笔记
1.获取内核源码 1.1Git 分布式的:下载和管理Linux内核源代码: - 获取最新提交到版本树的一个副本 $ git clone git://git.kernel.org/pub/scm/lin ...
- js实现进度条效果
需求分析: 最近学习javascript客户端脚本语言,学到了两个定时器函数setInterval()和setTimeout(),回想起以前在网页上看到的进度条效果可以使用定时器来实现,所以完成了进度 ...
- 【iMooc】全面解析java注解
在慕课上学习了一个关于java注解的课程,下面是笔记以及一些源码. Annotation——注解 1.JDK中的注解 JDK中包括下面三种注解: @Override:标记注解(marker annot ...
- Ubuntu16解锁root
administrator@rgqancy:~$ sudo passwd -u root [sudo] administrator 的密码: 对不起,请重试. [sudo] administrator ...
- delphi XE的字符串处理
最近用delphi xe做了个东西,因为以前一直使用Delphi 7做开发,delphi 7 到delphi XE有了很大的变化,最大的变化就是对Unicode的支持,所以刚开始使用DELPHI XE ...
- delphi JPG或BMP图片透明显示
procedure SaveBmpAsIcon(const Bmp: TBitmap; const Icon: string; const SmallIcon: Boolean; const Tran ...