P1156 垃圾陷阱
题意:
深度为 D D D 的坑内有一只牛,每个物品扔下的时间 t t t,高度 h h h,维持的生命的时间 v v v。每个物品都可以用来维持生命或者增加高度,询问最早出坑时间或者最长维持生命的时间
解析:
类似背包问题,垃圾的高度为体积,增加的生命为价值,坑深度为背包的体积。
设
d
p
i
,
j
dp_{i,j}
dpi,j 为前
i
i
i 个物品到达高度
j
j
j 的最长生命时间。对每个物品都有吃或者填两种情况。
吃:
d
p
i
,
j
=
m
a
x
(
d
p
i
,
j
,
d
p
i
−
1
,
j
+
a
[
i
]
.
v
−
(
a
[
i
]
.
t
−
a
[
i
−
1
]
.
t
)
dp_{i,j} = max(dp_{i,j}, dp_{i-1,j} + a[i].v -(a[i].t-a[i-1].t)
dpi,j=max(dpi,j,dpi−1,j+a[i].v−(a[i].t−a[i−1].t)
填:
d
p
i
,
j
=
m
a
x
(
d
p
i
,
j
,
d
p
i
−
1
,
j
−
a
[
i
]
.
h
−
(
a
[
i
]
.
t
−
a
[
i
−
1
]
.
t
)
dp_{i,j} = max(dp_{i,j}, dp_{i-1,j-a[i].h} -(a[i].t-a[i-1].t)
dpi,j=max(dpi,j,dpi−1,j−a[i].h−(a[i].t−a[i−1].t)
如果
d
p
i
,
j
≥
0
dp_{i,j} \ge 0
dpi,j≥0 且
j
≥
D
j \ge D
j≥D 则跳出坑。否则为
d
p
i
,
0
的最大值
dp_{i,0}的最大值
dpi,0的最大值
注意到每次转移只与上一个物品的状态有关,可以滚掉一维。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2510;
int dp[2][maxn+50];
int n, H;
struct node{
int t, f, h;
bool operator < (const node &b) const{
return t < b.t;
}
}a[maxn];
int main(){
cin >> H >> n;
for(int i = 1; i <= n; i++){
cin >> a[i].t >> a[i].f >> a[i].h;
}
sort(a+1, a+1+n);
memset(dp, -1, sizeof(dp));
dp[0][0] = 10; a[0].t = 0;
int ans = 10;
for(int i = 1; i <= n; i++){
int pre = (i-1)%2;
int cur = i%2;
memset(dp[cur], -1, sizeof(dp[cur]));
for(int j = 0; j <= H+10; j++){
int pre = (i-1)%2;
int cur = i%2;
if(dp[pre][j] >= (a[i].t-a[i-1].t))
dp[cur][j] = max(dp[cur][j], dp[pre][j]+a[i].f-(a[i].t-a[i-1].t));
if(j >= a[i].h)
dp[cur][j] = max(dp[cur][j], dp[pre][j-a[i].h]-(a[i].t-a[i-1].t));
if(dp[cur][j] >= 0 && j >= H){
cout << a[i].t;
return 0;
}
}
ans = max(ans, dp[cur][0]);
}
cout << ans << endl;
return 0;
}
P5322 [BJOI2019] 排兵布阵
题意:
没人有 m m m 个士兵,可以任意分配到 n n n 个城堡中,如果在第 i i i 的城堡己方士兵个数严格大于对方士兵数的两倍,获得 i i i 分。每次策略一致,已知其余玩家兵力情况,询问最大的分
解析:
对于城堡
i
i
i ,如果攻占成功,兵力更少的城堡也能攻占成功,可以快速计算得分。
d
p
i
,
j
dp_{i,j}
dpi,j为前
i
i
i个城堡派出
j
j
j士兵的最大的分。
转移方程:
d
p
j
=
m
a
x
{
d
p
j
−
a
[
i
]
[
k
]
∗
2
−
1
+
k
∗
i
}
dp_j = max \{dp_{j-a[i][k]*2-1}+k*i\}
dpj=max{dpj−a[i][k]∗2−1+k∗i}
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e4+10;
int dp[maxn];
int n, m, s;
int a[110][110];
int main(){
cin >> s >> n >> m;
for(int i = 1; i <= s; i++)
for(int j = 1; j <= n; j++)
cin >> a[j][i];
for(int i = 1; i <= n; i++)
sort(a[i]+1, a[i]+1+s);
for(int i = 1; i <= n; i++)
for(int j = m; j >= 0; j--)
for(int k = 1; k <= s; k++){
if(j >= a[i][k]*2+1)
dp[j] = max(dp[j], dp[j-a[i][k]*2-1]+k*i);
}
int ans = 0;
for(int i = 1; i <= m; i++)
ans = max(ans, dp[i]);
cout << ans << endl;
return 0;
}
P2466 [SDOI2008] Sue 的小球
题意:
二维平面上有许多点,一个人在原点,只能沿x轴运动。当运动到某个小球下方的时候,获得分数为当前小球纵坐标y的值。小球 i i i每秒下落 v i v_i vi个单位,询问收集所有球最高得分。
解析:
当前决策会对未来的费用产生影响。费用提前计算的思想,当前决策要考虑对未来的影响,从而进行状态的转移。
区间dp。
d
p
i
,
j
,
k
dp_{i,j,k}
dpi,j,k表示收集区间
[
l
,
r
]
[l,r]
[l,r]的球并且在左侧或者右侧的最高得分。对未来的影响是当前移动的时间与未收集小球速度和的乘积。利用前缀和,快速查询小球的速度和。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3+10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll sumv[maxn];
ll f[maxn][maxn][2];
struct node{
ll x, y, v;
bool operator < (const node &b) const{
return x < b.x;
}
}a[maxn];
ll n, x0, pos;
int main(){
cin >> n >> x0;
for(int i = 2; i <= n+1; i++)
cin >> a[i].x;
for(int i = 2; i <= n+1; i++)
cin >> a[i].y;
for(int i = 2; i <= n+1; i++)
cin >> a[i].v;
a[1] = {x0, 0, 0};
sort(a+1, a+n+2);
for(int i = 1; i <= n+1; i++){
sumv[i] = sumv[i-1]+a[i].v;
if(a[i].x == x0 && a[i].y == 0 && a[i].v == 0)
pos = i;
}
memset(f, -INF, sizeof(f));
f[pos][pos][0] = f[pos][pos][1] = 0;
for(int len = 1; len <= n+1; len++){
for(int i = 1; i+len <= n+1; i++){
int j = i+len;
f[i][j][0] = a[i].y + max(f[i+1][j][0]-(a[i+1].x-a[i].x)*(sumv[n+1]-sumv[j]+sumv[i]),
f[i+1][j][1]-(a[j].x-a[i].x)*(sumv[n+1]-sumv[j]+sumv[i]));
f[i][j][1] = a[j].y + max(f[i][j-1][0]-(a[j].x-a[i].x)*(sumv[n+1]+sumv[i-1]-sumv[j-1]),
f[i][j-1][1]-(a[j].x-a[j-1].x)*(sumv[n+1]+sumv[i-1]-sumv[j-1])) ;
}
}
double ans = max(f[1][n+1][1], f[1][n+1][0]);
ans /= 1000;
cout << setprecision(3) << fixed << ans << endl;
}
P3698 [CQOI2017]小Q的棋盘
题意:
给定一棵 n n n节点的树,从根节点出发,走 m m m步,最多经过多少节点。节点可以重复经过但不重复计数。
解析:
对于以
u
u
u为根的子树有两种可能,在
u
u
u的子树中走一圈最后回到
u
u
u,在
u
u
u的子树绕圈后向某个子树一直走
设
f
u
,
j
,
k
f_{u, j, k}
fu,j,k为从
u
u
u走
j
j
j步是否回头到达的最大节点数。不回头为0,回头为1
f
u
,
j
,
1
f_{u, j, 1}
fu,j,1只能由
f
v
,
j
−
2
,
1
f_{v, j-2, 1}
fv,j−2,1转移而来:
f
u
,
j
,
1
=
m
a
x
{
f
v
,
k
,
1
+
f
u
,
j
−
k
−
2
,
1
}
f_{u, j, 1} = max\{ f{v, k, 1}+f_{u, j-k-2, 1}\}
fu,j,1=max{fv,k,1+fu,j−k−2,1}
f
u
,
j
,
0
f_{u, j, 0}
fu,j,0,枚举节点
v
v
v时,有两种可能,在
v
v
v一直走,在
v
v
v之前的节点一直走。
在
v
v
v一直走:
f
u
,
j
,
0
=
m
a
x
{
f
u
,
j
−
k
−
1
,
0
+
f
v
,
k
,
0
}
f_{u, j, 0} = max\{ f_{u, j-k-1, 0}+f_{v, k, 0}\}
fu,j,0=max{fu,j−k−1,0+fv,k,0}
在
v
v
v之前一直走:
f
u
,
j
,
0
=
m
a
x
{
f
u
,
j
−
k
−
2
,
0
+
f
v
,
k
,
1
}
f_{u,j,0} = max\{ f_{u, j-k-2, 0}+f_{v, k, 1}\}
fu,j,0=max{fu,j−k−2,0+fv,k,1}
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2+10;
int f[maxn][maxn][2];
int head[maxn], tot;
struct edge{
int to, nxt;
}e[maxn<<1];
int n, m;
void add(int a, int b){
e[++tot].nxt = head[a];
e[tot].to = b;
head[a] = tot;
}
void dfs(int u, int fa){
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == fa)
continue;
dfs(v, u);
}
f[u][0][0] = f[u][0][1] = 1;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == fa)
continue;
for(int i = m; i >= 1; i--){
for(int j = 0; j < i; j++){
if(i >= j+2){
f[u][i][1] = max(f[u][i][1], f[u][i-j-2][1]+f[v][j][1]);
f[u][i][0] = max(f[u][i][0], f[u][i-j-2][0]+f[v][j][1]);
}
f[u][i][0] = max(f[u][i][0], f[u][i-j-1][1]+f[v][j][0]);
}
}
}
}
int main(){
cin >> n >> m;
for(int i = 1; i <= n-1; i++){
int a, b;
cin >> a >> b;
a++, b++;
add(a, b); add(b, a);
}
dfs(1, 0);
int ans = 0;
for(int i = 1; i <= m; i++)
ans = max(ans, max(f[1][i][0], f[1][i][1]));
cout << ans << endl;
return 0;
}