动态规划(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. HDU 2586 /// tarjan离线求树上两点的LCA

    题目大意: 询问一棵树里 u 到 v 的距离 可由 dis[ u到根 ] + dis[ v到根 ] - 2*dis[ lca(u,v) ] 得到 https://blog.csdn.net/csyzc ...

  2. Codeforces 479【B】div3

    题目链接: http://codeforces.com/problemset/problem/977/B 题意:字符串,找固定长度为2的重复子串出现次数最多的. 题解:我暴力做的.暴力出奇迹. #in ...

  3. CSIC_716_20191119【常用模块的用法 subprocess、re、logging、防止自动测试、包的理论】

    subprocess模块 可以通过python代码给操作系统终端发送命令,并可以得到返回结果. import subprocess str = input('>>>请输入命令') # ...

  4. 如何设置树莓派 VNC 的分辨率

    当我们使用 VNC 连接到树莓派时,默认的分辨率非常低.甚至无法显示整个桌面,因此我们需要对分辨率进行设置.在树莓派上设置 VNC 的分辨率很简单,在终端运行下面指令进入设置界面设置. 1 sudo ...

  5. 解析Spring第一天

    目的:了解spring框架中的注解 前言:同样是使用idea创建一个普通的maven工程(如何创建一个普通的Maven工程可以参考mybatis入门第一天的详解). 项目结构: 代码编辑: 在项目中引 ...

  6. TopCoder代码格式模板

    $BEGINCUT$ $PROBLEMDESC$ $ENDCUT$ #include<bits/stdc++.h> using namespace std; class $CLASSNAM ...

  7. 期望dp+高斯消元——bzoj3143

    比较经典的题,题解看网上的..https://www.cnblogs.com/GXZlegend/p/7054536.html 自己sort弄错了..还以为是高斯消元写歪了.. #include< ...

  8. 生成器yield(17-06)

    yield  执行以上代码,yield后面可以有返回值 next() 获取 next的使用次数,是你生成器中yield出现的次数 def p(): print("ok") yiel ...

  9. 19.SimLogin_case01

    什么是模拟登录? 要抓取的信息,只有在登录之后才能查看.这种情况下,就需要爬虫做模拟登录,绕过登录页. cookies和session的区别: cookie数据存放在客户的浏览器上,session数据 ...

  10. Linux使用crontab定时执行Python脚本清理日志

    Linux中,周期执行的任务一般由crond这个守护进程来处理.cron读取一个或多个配置文件,这些配置文件中包含了命令行及其调用时间.crond的配置文件称为"crontab", ...