动态规划(DP)

// 以下题目来自牛客网

删括号

f[i][j][k] 表示序列s的前i个匹配序列t的前j个,序列s删除部分左括号与右括号数量差为k的情况是否可行

答案为 f[sl][tl][0]

状态转移:

当 f[i][j][k] 可行时

  1. s[i+1]==t[j+1] 且 k==0 则 f[i+1][j+1][k] = 1
  2. s[i+1]=='('  则s串删去当前括号可匹配,即 f[i+1][j][k+1] = 1
  3. s[i+1]==')'  则 k>0 时s串多删去一个左括号匹配,即 f[i+1][j][k-1] = 1
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std; bool f[][][]; //s前i个删去括号能否成为t前j个,左右括号差为k
char s[], t[];
int main() {
scanf("%s", s+);
scanf("%s", t+);
int sl = strlen(s+), tl = strlen(t+);
f[][][] = ; for(int i=;i<sl;i++) {
for(int j=;j<tl;j++) {
for(int k=;k<sl;k++) if(f[i][j][k]) {
if(k== && s[i+]==t[j+]) f[i+][j+][k] = ;
if(s[i+]=='(') f[i+][j][k+] = ;
else if(k) f[i+][j][k-] = ;
}
}
}
printf("%s\n", f[sl][tl][]?"Possible":"Impossible");
return ;
}

回文子序列计数

错误思路:x[i] = 左右26个小写字母选取0~min(l[i][j], r[i][j]) (0<=j<26)的组合数之积。

正确求法:见代码。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int mod = 1e9+;
typedef long long ll; ll x[], dp[]; // dp[i]位回文子序列个数
// dp[i+1]
char s[];
int main()
{
scanf("%s", s);
int n = strlen(s);
for(int i=;i<n;i++) x[i] = ;
for(int i=;i<n;i++) {
ll sum = , tmp;
for(int j=n-;j>i;j--) {
tmp = dp[j];
if(s[j]==s[i-]) {
dp[j] = (dp[j] + sum + ) % mod;
}
sum = (sum + tmp) % mod;
x[i] = (x[i] + dp[j]) % mod;
}
} ll ans = ;
for(int i=;i<n;i++) {
ans = ans^((i+) * x[i]) % mod;
}
printf("%lld\n", ans);
return ;
}

牛牛的计算机内存

状压dp

直接 dp[22][1<<20] 会MLE,只能用滚动数组记录状态。

int dp[1<<20];     // dp[S]: 前i条指令状态为S的最小代价
int state[1<<20]; // state[i]:j 指令状态i执行完后的内存状态为j

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
int dp[<<]; // dp[S]: 前i条指令,访问完状态为S的最小代价
int state[<<]; // state[i]:S 前i条指令执行完状态为S
int a[]; int main() { memset(dp, INF, sizeof(dp));
dp[] = ; int n, m;
char ins[];
scanf("%d %d", &n, &m);
for(int i=;i<n;i++) {
scanf("%s", ins);
int k = ;
for(int j=;j<m;j++) {
a[i] = a[i]* + (ins[j]-'');
if(ins[j]=='') ++k;
}
state[<<i] = a[i];
dp[<<i] = k*k;
} for(int S=;S<(<<n);S++) {
if(dp[S]==INF) continue; for(int i=;i<n;i++) {
if((S>>i)&) continue; int nexS = S|(<<i), k = ;
for(int j=;j<m;j++) {
if((a[i]>>j)& && ((state[S]>>j)&)==) {
++k;
}
}
if(dp[nexS]>dp[S]+k*k) {
dp[nexS] = dp[S] + k*k;
state[nexS] = state[S]|a[i];
}
}
} printf("%d\n", dp[(<<n)-]);
return ;
}

棋盘的必胜策略

可以用 f[i][j][step] 记录到 mp[i][j] 用了step步的胜负状态,dfs即可。

  1. 如果下一步有必败态,当前则为必胜态
  2. 否则当前为必败态
  3. mp[i][j]终点为必败态
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std; const int dx[] = {, , , -};
const int dy[] = {, -, , }; int r, c, k;
char mp[][];
int f[][][]; bool check(int x, int y) {
if(x<||y<||x>=r||y>=c)
return false;
if(mp[x][y]=='#')
return false;
return true;
} int dfs(int x, int y, int k) {
if(f[x][y][k]!=-)
return f[x][y][k];
if(mp[x][y]=='E') // 走到终点,无法移动,必败
return f[x][y][k] = ;
if(k==)
return ; // 走不了,必败 for(int i=;i<;i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if(check(nx, ny) && dfs(nx, ny, k-)==)
return f[x][y][k] = ;
}
return f[x][y][k] = ;
} int main() {
cin>>r>>c>>k;
for(int i=; i<r; i++)
scanf("%s",mp[i]);
memset(f, -, sizeof(f)); int sx, sy;
for(int i=;i<r;i++) {
for(int j=;j<c;j++) {
if(mp[i][j] == 'T') {
sx = i;
sy = j;
}
}
} printf("%s\n", dfs(sx, sy, k)?"niuniu":"niumei");
return ;
}

看起来像博弈论,其实分析一下最多走两步就能确定胜负,不用搜索状态也能解决。

分析见代码。

#include<iostream>
#include<cstdio>
using namespace std; const int dx[] = {, , , -};
const int dy[] = {, -, , }; int r, c, k;
char mp[][];
bool check(int x, int y) {
if(x<||y<||x>=r||y>=c)
return false;
if(mp[x][y]=='#')
return false;
return true;
}
bool win(int x, int y) {
for(int i=;i<;i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if(check(nx, ny) && mp[nx][ny]=='E')
return true;
}
return false;
}
int main() {
cin>>r>>c>>k;
for(int i=; i<r; i++)
scanf("%s",mp[i]); int sx, sy;
for(int i=;i<r;i++) {
for(int j=;j<c;j++) {
if(mp[i][j] == 'T') {
sx = i;
sy = j;
}
}
} bool f = false; // 第一步能否走
for(int i=;i<;i++) {
int nx = sx + dx[i];
int ny = sy + dy[i];
if(check(nx, ny)) {
f = true;
if(mp[nx][ny]=='E')
return * printf("niuniu\n");
}
}
if(!f) { // 动不了
return * printf("niumei\n");
}
if(k==) { // 只走一步
return * printf("niuniu\n");
}
if(k%==) { // 偶数步,往返走,走后必胜
return * printf("niumei\n");
}
// 奇数步,第二步无法胜,第三步开始往返走,先走必胜
for(int i=;i<;i++) {
int nx = sx + dx[i];
int ny = sy + dy[i];
if(check(nx, ny) && mp[nx][ny]=='.' && !win(nx, ny)) {
return * printf("niuniu\n");
}
}
puts("niumei");
return ;
}

牛牛与数组

状态转移很好写,记录一下前缀和,减去dp[i-1][j] j的整数倍的部分即为dp[i][j]

#include<iostream>
#include<cstdio>
using namespace std;
const int mod = 1e9+;
int dp[][];
int main() {
int n, k;
scanf("%d %d", &n, &k); for(int i=;i<=k;i++) dp[][i] = ; for(int i=;i<=n;i++) {
int sum = ;
for(int j=;j<=k;j++)
sum = (sum + dp[i-][j]) % mod; for(int j=;j<=k;j++) {
int sum1 = ;
for(int l=*j;l<=k;l+=j) {
sum1 = (sum1 + dp[i-][l])% mod;
}
dp[i][j] = ((sum - sum1)%mod+mod)%mod;
} } printf("%d\n", dp[n][k]);
return ;
}

牛牛去买球

n个盒子,每个盒子有a[i]个红球,b[i]个篮球,但a[i],b[i]有正负1的偏差,总和不变。买每个盒子的费用为c[i],求买k个相同的球的最小花费。

三种情况

  1. 买k个红球,每个盒子都当做a[i]-1个红球
  2. 买k个蓝球,每个盒子都当做b[i]-1个蓝球
  3. 买2k-1个球,至少保证有k个相同颜色的球

用滚动数组上限为最多的球数,而不是k。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; int dp[];
int a[], b[], c[];
int main() {
int n, k; cin>>n>>k;
for(int i=;i<=n;i++)
scanf("%d", &a[i]);
for(int i=;i<=n;i++)
scanf("%d", &b[i]);
for(int i=;i<=n;i++)
scanf("%d", &c[i]); int ans = 0x3f3f3f3f, up = ;
memset(dp, 0x3f, sizeof(dp));
dp[] = ;
for(int i=;i<=n;i++) {
int v = a[i] - ;
for(int j=up;j>=v;j--) {
dp[j] = min(dp[j], dp[j-v]+c[i]);
}
}
for(int i=k;i<=*k;i++) ans = min(ans, dp[i]); memset(dp, 0x3f, sizeof(dp));
dp[] = ;
for(int i=;i<=n;i++) {
int v = b[i] - ;
for(int j=up;j>=v;j--) {
dp[j] = min(dp[j], dp[j-v]+c[i]);
}
}
for(int i=k;i<=*k;i++) ans = min(ans, dp[i]); memset(dp, 0x3f, sizeof(dp));
dp[] = ;
for(int i=;i<=n;i++) {
int v = a[i]+b[i];
for(int j=up;j>=v;j--) {
dp[j] = min(dp[j], dp[j-v]+c[i]);
}
}
for(int i=*k-;i<=up;i++) ans = min(ans, dp[i]);
if(ans==0x3f3f3f3f) ans = -;
printf("%d\n", ans);
return ;
}

小明打联盟

有3个小技能一个大招,大招的伤害值随时间线性变化。给定T时间,以及各个技能的释放时间和伤害值,问最大的伤害值是多少。

不考虑大招的话,就是多重背包问题。

把一个大招看成两个L, R时刻释放的大招d, e,中间时刻释放只会用一次。 (假设用两次m时刻的大招可以转化为大招e +  (2m-l)时刻的大招,还是相当于用一次)

然后再枚举L,R区间的最大伤害值即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std; int t;
int v[];
int w[];
long long dp[];
int main() {
while(scanf("%d", &t)!=EOF) {
for(int i=;i<;i++) {
scanf("%d %d", &v[i], &w[i]);
}
int L, R, temp, A;
scanf("%d %d %d %d", &L, &R, &temp, &A);
v[] = L; w[] = temp;
v[] = R; w[] = temp + A*(R-L); memset(dp, , sizeof(dp));
for(int i=;i<;i++) {
for(int j=v[i];j<=t;j++) { // 多重背包
dp[j] = max(dp[j], dp[j-v[i]]+w[i]);
}
}
for(int j=L;j<=R;j++) {
dp[t] = max(dp[t], dp[t-j]+temp+1LL*A*(j-L));
} printf("%lld\n", dp[t]);
} return ;
}

树形dp

// 以下题目来自洛谷

P1352 没有上司的舞会

状态转移方程很简单,1A

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std; int n, fa[];
int w[];
vector<int> G[]; int dp[][];
// dp[u][0] u没有参加
// dp[u][1] u参加 void dfs(int u, int fa) {
dp[u][] = w[u];
for(int i=;i<G[u].size();i++) {
int v = G[u][i];
if(v==fa) continue; dfs(v, u);
dp[u][] += dp[v][];
dp[u][] += max(dp[v][], dp[v][]);
}
} int main() {
scanf("%d", &n);
for(int i=;i<=n;i++)
scanf("%d", &w[i]); int u, v;
for(int i=;i<n;i++) {
scanf("%d %d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
fa[u] = v;
} int rt = -;
for(int i=;i<=n;i++)
if(!fa[i]) {
rt = i;
break;
}
dfs(rt, -);
printf("%d\n", max(dp[rt][], dp[rt][]));
return ;
}

P2016 战略游戏

选出一棵树上最少的节点,能覆盖所有边。

这题结构跟上面类似,每一点放/不放两个状态。

查看题解有大佬指出这是最小点覆盖问题,使用匈牙利算法,对于无向图答案为 ans / 2 。

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = ; vector<int> G[maxn];
int n;
int f[maxn][]; void dfs(int u, int fa) {
f[u][] = ;
for(int i=;i<G[u].size();i++) {
int v = G[u][i];
if(v==fa) continue; dfs(v, u); f[u][] += f[v][];
f[u][] += min(f[v][], f[v][]);
}
} int main() {
scanf("%d", &n);
for(int i=;i<n;i++) {
int u, v, k;
scanf("%d %d", &u, &k);
while(k--) {
scanf("%d", &v);
G[u].push_back(v);
G[v].push_back(u);
} }
dfs(, -);
printf("%d\n", min(f[][], f[][]));
return ;
}

P2015 二叉苹果树

保留K条边苹果树上的最大苹果数量。

注意子树边的数量写法:dfs儿子后 sz[u] += sz[v] + 1;

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = ; int n, K;
struct Edge {
int to, w;
Edge(int v, int ww):to(v), w(ww){}
};
vector<Edge> G[maxn];
int sz[maxn];
int dp[maxn][maxn];
// dp[u][i] : 以u为根的子树保留i条边的最多苹果数量 void dfs(int u, int fa) {
for(int i=;i<G[u].size();i++) {
int v = G[u][i].to;
if(v==fa) continue; dfs(v, u);
sz[u] += sz[v] + ; // 边的数量 for(int j=min(sz[u], K);j>=;j--) { // 01背包,逆序
for(int k=;k<=min(sz[v], j-);k++) {
dp[u][j] = max(dp[u][j], dp[u][j-k-] + dp[v][k] + G[u][i].w);
}
} }
} int main() {
scanf("%d %d", &n, &K); int u, v, w;
for(int i=;i<n;i++) {
scanf("%d %d %d", &u, &v, &w);
G[u].push_back(Edge(v, w));
G[v].push_back(Edge(u, w));
} dfs(, -);
printf("%d\n", dp[][K]);
return ;
}

P2014 选课

课程之间有依赖关系,求选M门课程的最大学分。

将没有直接先修课的课程连在根为 0 的树上,从节点 0 dfs 即可。

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = ; int n, K;
vector<int> G[maxn];
int sz[maxn], w[maxn];
int dp[maxn][maxn];
// dp[u][i] : 以u为根的子树选i门课的最大学分 void dfs(int u) {
sz[u] = ;
for(int i=;i<G[u].size();i++) {
int v = G[u][i]; dfs(v);
sz[u] += sz[v]; for(int j=min(sz[u], K);j>=;j--) {
for(int k=;k<=min(j-, sz[v]);k++) {
dp[u][j] = max(dp[u][j], dp[u][j-k-] + dp[v][k]);
}
} }
} int main() {
scanf("%d %d", &n, &K); int fa;
for(int i=;i<=n;i++) {
scanf("%d %d", &fa, &w[i]);
G[fa].push_back(i);
} for(int i=;i<=n;i++) dp[i][] = w[i];
dfs();
printf("%d\n", dp[][K]);
return ;
}

P1270 “访问”美术馆

读入采用dfs形式给出美术馆的通过走廊的时间和藏画数量,问T时间内能盗窃多少幅画。

坑点:时间有效时间为 T - 1

记搜 / 树形dp 。由于要返回根节点,时间可以直接乘以 2 读入。

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = ; int T, tot;
struct node {
int cost, val;
}tree[maxn*];
int dp[maxn*][]; void dfs(int u, int t) {
if(dp[u][t] || t==) return; // 0为0直接返回 if(tree[u].val) { // 根节点
dp[u][t] = min(tree[u].val, (t-tree[u].cost)/);
return;
} for(int i=;i<=t-tree[u].cost;i++) {
dfs(u*, i);
dfs(u*+, t-i-tree[u].cost); // 右边剩下时间= t - i - 2倍走廊时间 dp[u][t] = max(dp[u][t], dp[u*][i]+dp[u*+][t-i-tree[u].cost]); }
} void build(int rt) {
scanf("%d %d", &tree[rt].cost, &tree[rt].val);
tree[rt].cost *= ;
if(!tree[rt].val) {
build(rt*);
build(rt*+);
}
} int main() { scanf("%d", &T);
build(); dfs(, T-); printf("%d\n", dp[][T-]);
return ;
}

数位DP

// 以下来自洛谷

P2657 [SCOI2009]windy数

求A,B区间内满足相邻两位数字之差大于等于2的整数个数。

注意是在 !lim && !zero 条件下记忆化,没加这个条件调了半天。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll; ll dp[][]; // dp[i][j]:长度为i中最高位是j的windy数的个数
int bit[];
ll dfs(int pos, int lim, int last, int zero) {
if(pos<) return ; if(!lim && !zero && dp[pos][last]!=-) return dp[pos][last]; int res = ;
int up = lim?bit[pos]:;
for(int i=;i<=up;i++) {
if(abs(i-last)<) continue; res += dfs(pos-, lim&&(i==up), zero&&(i==)?:i, zero&&(i==));
}
if(!lim && !zero) dp[pos][last] = res;
return res;
} ll cal(ll x) {
int cnt = ;
while(x) {
bit[cnt++] = x%;
x /= ;
}
memset(dp, -, sizeof(dp));
return dfs(cnt-, , , );
} int main() {
ll A, B; while(cin>>A>>B) printf("%lld\n", cal(B)-cal(--A)); return ;
}

洛谷题解翻到别人的代码处理:

dp练习集的更多相关文章

  1. dp合集 广场铺砖问题&&硬木地板

    dp合集 广场铺砖问题&&硬木地板 很经典了吧... 前排:思想来自yali朱全民dalao的ppt百度文库免费下载 后排:STO朱全民OTZ 广场铺砖问题 有一个 W 行 H 列的广 ...

  2. 9.15 DP合集水表

    9.15 DP合集水表 显然难了一些啊. 凸多边形的三角剖分 瞄了一眼题解. 和蛤蛤的烦恼一样,裸的区间dp. 设f[i][j]表示i~j的点三角剖分最小代价. 显然\(f[i][i+1]=0,f[i ...

  3. 9.14 DP合集水表

    9.14 DP合集水表 关键子工程 在大型工程的施工前,我们把整个工程划分为若干个子工程,并把这些子工程编号为 1. 2. --. N:这样划分之后,子工程之间就会有一些依赖关系,即一些子工程必须在某 ...

  4. 数位dp题集

    题集见大佬博客 不要62 入门题,检验刚才自己有没有看懂 注意一些细节. 的确挺套路的 #include<bits/stdc++.h> #define REP(i, a, b) for(r ...

  5. TYVJ1071 LCIS 线性DP+决策集优化

    问题描述 TYVJ1071 题解 暴力\(\mathrm{DP}\) 首先,一个\(O(n^3)\)的解法: 设\(opt_{i,j}\)代表\(a\)的前\(i\)个和\(b\)的前\(j\)个的\ ...

  6. 杭电dp题集,附链接还有解题报告!!!!!

    Robberies 点击打开链接 背包;第一次做的时候把概率当做背包(放大100000倍化为整数):在此范围内最多能抢多少钱  最脑残的是把总的概率以为是抢N家银行的概率之和- 把状态转移方程写成了f ...

  7. 【CJOJ2498】【DP合集】最长上升子序列 LIS

    题面 Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的n个整数组成的序列 ...

  8. CJOJ 【DP合集】最长上升序列2 — LIS2

    题面 已知一个 1 ∼ N 的排列的最长上升子序列长度为 K ,求合法的排列个数. 好题(除了我想不出来我应该找不到缺点), 想一想最长上升子序列的二分做法, 接在序列后面或者替换. 所以对于每一个位 ...

  9. DP 题集 2

    关于 DP 的一些题目 String painter 先区间 DP,\(dp[l][r]\) 表示把一个空串涂成 \(t[l,r]\) 这个子串的最小花费.再考虑 \(s\) 字符串,\(f[i]\) ...

  10. DP 题集 1

    关于 DP 的一些题目 参考资料 [Tutorial] Non-trivial DP Tricks and Techniques DP Rain and Umbrellas Mr. Kitayuta, ...

随机推荐

  1. xwiki系统 知识库 xwiki

    1.下载tomcat tar  -zxvf  apache-tomcat-8.0.14.tar.gz mv apache-tomcat-8.0.14 tomcat-xwiki-8.0 2.下载xwik ...

  2. java笔试之查找组成一个偶数最接近的两个素数

    任意一个偶数(大于2)都可以由2个素数组成,组成偶数的2个素数有很多种情况,本题目要求输出组成指定偶数的两个素数差值最小的素数对. package test; import java.util.Sca ...

  3. Twain协议部分翻译

    转载:https://blog.csdn.net/a848691591/article/details/41006807 4.1 性能 应用程序与源进行性能协商的能力使人们能够控制TWAIN兼容的程序 ...

  4. Java 基础 - Object.clone()深拷贝和浅拷贝

    作者:YSOcean 出处:http://www.cnblogs.com/ysocean/ 本文版权归作者所有,欢迎转载,但未经作者同意不能转载,否则保留追究法律责任的权利.   ---------- ...

  5. [JZOJ3402] 【GDOI2014模拟】Pty的字符串

    题目 给你一棵每条边从父亲指向儿子的树,每条边上面有一个字母. 从树上的任意一点出发,走出的路径就是对应一个子串. (这不是\(Trie\),因为每个父亲可能会连出字母相同的边) 再给你一个字符串\( ...

  6. cocos2D-X LUA 非常简单的一个贪吃蛇案例

    --[[ 贪吃蛇 ]] local RetroSnaker = class("RetroSnaker", function() return cc.Layer:create(); ...

  7. 专访阿里云MVP黄胜蓝:90 后 CTO花了6年,改变了你日常生活里的这件事

    [黄胜蓝:现任武汉极意网络科技有限公司CTO.高中时期NOIP一等奖保送至武汉大学,大学期间曾指导团队获得世界数学建模大赛金奖,同时负责武汉大学学生校园门户网站的运维工作.于2013年加入武汉极意网络 ...

  8. Eclipse中如何使用Hibernate

    首先创建一个java web项目,其目录如下: (1)创建文件夹hibernate4(用于存放下载的hibernate工具包lib/required文件夹下所有的jar包),jar包目录如下: (2) ...

  9. 初步了解Redis

    参考: https://juejin.im/post/5b4dd82ee51d451925629622?utm_source=gold_browser_extension https://www.cn ...

  10. js 截取url中的参数

    getQueryString(name) { var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); var ...