数字三角形模型
1015. 摘花生 - AcWing题库 (单条路线)
简单
d p [ i ] [ j ] dp[i][j] dp[i][j]表示从(1,1)走到(i,j)的最大花生数量
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + b [ i ] [ j ] dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + b[i][j] dp[i][j]=max(dp[i−1][j],dp[i][j−1])+b[i][j]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 105;
int T, R, C;
int dp[MAXN][MAXN], b[MAXN][MAXN];
int main()
{
scanf("%d",&T);
while(T--)
{
memset(dp,0,sizeof(dp));
scanf("%d%d",&R,&C);
for(int i = 1;i <= R;i++)
{
for(int j = 1;j <= C;j++)
{
scanf("%d",&b[i][j]);
}
}
for(int i = 1;i <= R;i++)
{
for(int j = 1;j <= C;j++)
{
dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + b[i][j];
}
}
printf("%d\n",dp[R][C]);
}
return 0;
}
1018. 最低通行费 - AcWing题库 (单条路线+边界判断)
只能往下或往右走,注意边界。
第一排只能从左边走过来
第一列只能从上面走下来
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 105;
int b[MAXN][MAXN], dp[MAXN][MAXN], n;
int main()
{
memset(dp,0,sizeof(dp));
scanf("%d",&n);
for(int i = n;i >= 1;i--)
{
for(int j = 1;j <= n;j++)
{
scanf("%d",&b[i][j]);
}
}
for(int i = n;i >= 1;i--)
{
for(int j = 1;j <= n;j++)
{
if(i == n) dp[i][j] = dp[i][j-1] + b[i][j];
else if(j == 1) dp[i][j] = dp[i+1][j] + b[i][j];
else dp[i][j] = min(dp[i+1][j],dp[i][j-1]) + b[i][j];
}
}
printf("%d\n",dp[1][n]);
return 0;
}
1027. 方格取数 - AcWing题库 (两条路线)
题意:从(1,1)走到终点的两条路径最大值,两条路径不能重复
f [ i 1 , j 1 , i 2 , j 2 ] f[i1,j1,i2,j2] f[i1,j1,i2,j2]表示从(1,1)到(i1,j1)与(1,1)到(i2,j2)的路径最大值
如何处理走过的格子不重复
只有在 i 1 + j 1 = i 2 + j 2 i_1+j_1 = i_2+j_2 i1+j1=i2+j2的时候走过的方格才能重合
进行优化
f [ k , i 1 , i 2 ] f[k,i1,i2] f[k,i1,i2]表示从(1,1)到(i1, k-i1)与(1,1)到(i2,k-i2)的路径最大值
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 11;
int n;
int w[MAXN][MAXN], dp[2*MAXN][MAXN][MAXN];
int main()
{
scanf("%d",&n);
while(true)
{
int a, b, c;
scanf("%d%d%d",&a,&b,&c);
if(!a && !b && !c) break;
w[a][b] = c;
for(int k = 2;k <= n+n;k++)
{
for(int i1 = 1;i1 <= n;i1++)
{
for(int i2 = 1;i2 <= n;i2++)
{
int j1 = k-i1, j2 = k-i2;
if(j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
{
int add = w[i1][j1];
if(i1 != i2) add += w[i2][j2];
int &x = dp[k][i1][i2];
x = max(x, dp[k-1][i1-1][i2-1] + add);
x = max(x, dp[k-1][i1][i2-1] + add);
x = max(x, dp[k-1][i1-1][i2] + add);
x = max(x, dp[k-1][i1][i2] + add);
}
}
}
}
}
int ans = dp[n+n][n][n];
printf("%d\n",ans);
return 0;
}
275. 传纸条 - AcWing题库 (两条路线+思维)
求(1,1)到(m,n)的最大两条路线,两条路线不能经过同一点
与上题一模一样,证明如下
如线有交叉:可如下处理
处理后仍可能通过相同的点
第二次通过C权值为0,A和B的权值>=0,两次通过同一点的路线并不会使总和更大。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n, m;
int w[55][55], dp[101][55][55];
int main()
{
scanf("%d%d",&m,&n);
for(int i = 1;i <= m;i++)
{
for(int j = 1;j <= n;j++)
{
scanf("%d",&w[i][j]);
}
}
for(int k = 2;k <= n+m;k++)
{
for(int i1 = 1;i1 <= m;i1++)
{
for(int i2 = 1;i2 <= m;i2++)
{
int j1 = k-i1, j2 = k-i2;
if(j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
{
int t = w[i1][j1];
if(i1 != i2) t += w[i2][j2];
int &x = dp[k][i1][i2];
x = max(x, dp[k-1][i1-1][i2-1] + t);
x = max(x, dp[k-1][i1][i2-1] + t);
x = max(x, dp[k-1][i1-1][i2] + t);
x = max(x, dp[k-1][i1][i2] + t);
}
}
}
}
int ans = dp[n+m][m][m];
printf("%d\n",ans);
return 0;
}
最长上升子序列模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AAkCY4sw-1629966810410)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210725104643636.png)]
1017. 怪盗基德的滑翔翼 - AcWing题库 (模板)
题意:从序列中某一点开始向两端的最长下降子序列。
从头开始和从尾开始跑一遍最长上升子序列。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 101;
int T, n, a[MAXN], b[MAXN], c[MAXN], id;
int main()
{
scanf("%d",&T);
while(T--)
{
int ans = 1;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
scanf("%d",&n);
id = 0;
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
b[id++] = a[1];
for(int i = 2;i <= n;i++)
{
if(a[i] <= b[id-1])
{
int pos = lower_bound(b,b+id,a[i])-b;
b[pos] = a[i];
}
else b[id++] = a[i];
}
ans = max(ans,id);
id = 0;
c[id++] = a[n];
for(int i = n-1;i >= 1;i--)
{
if(a[i] <= c[id-1])
{
int pos = lower_bound(c,c+id,a[i])-c;
c[pos] = a[i];
}
else c[id++] = a[i];
}
ans = max(ans,id);
printf("%d\n",ans);
}
return 0;
}
1014. 登山 - AcWing题库 (先上升后下降,枚举top)
题意:登山,先上升后下降,不能连续游览相同高度的两个景点
枚举峰值的点。前后各跑一个最长上升子序列,后一段满足下降时的最大值不超过top O ( n 2 l o g n ) O(n^2logn) O(n2logn)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1001;
int n, a[MAXN], b[MAXN], c[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
int ans = 0;
for(int i = 1;i <= n;i++)
{
int top = i;
int id = 0, id2 = 0;
b[id++] = a[1];
for(int j = 2;j <= top;j++)
{
if(a[j] <= b[id-1])
{
int pos = lower_bound(b,b+id,a[j]) - b;
b[pos] = a[j];
}
else b[id++] = a[j];
}
for(int j = n;j >= top+1;j--)
{
if(a[j] < b[id-1])
{
if(a[j] <= c[id2-1])
{
int pos = lower_bound(c,c+id2,a[j]) - c;
c[pos] = a[j];
}
else c[id2++] = a[j];
}
}
ans = max(ans,id+id2);
}
printf("%d\n",ans);
return 0;
}
可以先打表判断每个点开始的最长上升子序列和最长下降子序列,最后进行相加。复杂度 O ( n log n ) O(n \log n) O(nlogn)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int M = 1001;
int n, a[M], f[M], g[M], b[M], c[M], id;
int main()
{
scanf("%d",&n);
int ans = 0;
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
b[id++] = a[1], f[1] = 1;
for(int i = 2;i <= n;i++)
{
if(a[i] <= b[id-1])
{
int pos = lower_bound(b,b+id,a[i]) - b;
b[pos] = a[i];
f[i] = id;
}
else {
b[id++] = a[i];
f[i] = id;
}
}
int id = 0;
g[n] = 1, c[id++] = a[n];
for(int i = n-1;i >= 1;i--)
{
if(a[i] <= c[id-1])
{
int pos = lower_bound(c,c+id,a[i]) - c;
c[pos] = a[i];
g[i] = id;
}
else {
c[id++] = a[i];
g[i] = id;
}
}
for(int i = 1;i <= n;i++)
{
ans = max(ans,f[i]+g[i]-1);
}
printf("%d\n",ans);
return 0;
}
482. 合唱队形 - AcWing题库
思路与上题一样
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 101;
int n, a[MAXN], b[MAXN], f[MAXN], c[MAXN], g[MAXN], id;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
b[id++] = a[1], f[1] = 1;
for(int i = 2;i <= n;i++)
{
if(a[i] <= b[id-1])
{
int pos = lower_bound(b,b+id,a[i]) - b;
b[pos] = a[i];
f[i] = id;
}
else
{
b[id++] = a[i];
f[i] = id;
}
}
id = 0;
c[id++] = a[n], g[n] = 1;
for(int i = n-1;i >= 1;i--)
{
if(a[i] <= c[id-1])
{
int pos = lower_bound(c,c+id,a[i]) - c;
c[pos] = a[i];
g[i] = id;
}
else
{
c[id++] = a[i];
g[i] = id;
}
}
int ans = 1e9;
for(int i = 1;i <= n;i++)
{
ans = min(ans,n-f[i]-g[i]+1);
}
printf("%d\n",ans);
return 0;
}
1012. 友好城市 - AcWing题库 (排序+模板)
按照左端点排序,右端点最长上升子序列
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 5005;
int n, b[MAXN], id;
struct node{
int l, r;
bool operator<(const node &a)
{
return l < a.l;
}
}N[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
int l, r;
scanf("%d%d",&l,&r);
N[i].l = l;
N[i].r = r;
}
sort(N+1,N+1+n);
//for(int i = 1;i <= n;i++) printf("%d %d\n",N[i].l,N[i].r);
b[id++] = N[1].r;
for(int i = 2;i <= n;i++)
{
if(N[i].r < b[id-1])
{
int pos = lower_bound(b,b+id,N[i].r) - b;
b[pos] = N[i].r;
}
else b[id++] = N[i].r;
}
printf("%d\n",id);
return 0;
}
1016. 最大上升子序列和 - AcWing题库 (sum)
题意:求一个序列上升子序列之和最大值
用 s u m [ i ] sum[i] sum[i]表示以 a [ i ] a[i] a[i]结尾的上升子序列之和最大值
s u m [ i ] = m a x ( s u m [ j ] + a [ i ] ) ( a [ i ] > a [ j ] ) sum[i] = max(sum[j]+a[i]) \quad (a[i] > a[j]) sum[i]=max(sum[j]+a[i])(a[i]>a[j])
初始化 s u m [ i ] = a [ i ] sum[i] = a[i] sum[i]=a[i]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1010;
int n, dp[MAXN], sum[MAXN], a[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
dp[1] = 1, sum[1] = a[1];
for(int i = 2;i <= n;i++)
{
sum[i] = a[i], dp[i] = 1;
for(int j = 1;j < i;j++)
{
if(a[i] > a[j])
{
sum[i] = max(sum[j] + a[i], sum[i]);
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
int ans = sum[1];
for(int i = 1;i <= n;i++) ans = max(ans,sum[i]);
printf("%d\n",ans);
return 0;
}
1010. 拦截导弹 - AcWing题库 (贪心+模板)
题意:对于每个导弹拦截系统,它只能拦截比前一个导弹高度小的导弹,问最少要多少个拦截系统。
从头到尾与从尾到头最长上升子序列
贪心的思想:对于每个导弹,要么开辟一个新的系统,要么把他放到系统中第一个比他大的末尾,这样保证每个系统末尾的高度为单调上升的。
此时,需要的系统数为序列最长上升子序列。
注意lower_bound
与upper_bound
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1010;
int n, a[MAXN], dp1[MAXN], dp2[MAXN], id;
int main()
{
while(cin >> a[++id]);
int cnt1 = 0, cnt2 = 0;
dp1[cnt1++] = a[1], dp2[cnt2++] = a[id-1];
for(int i = 2;i <= id-1;i++)
{
if(a[i] <= dp1[cnt1-1])
{
int pos = lower_bound(dp1,dp1+cnt1,a[i]) - dp1;
dp1[pos] = a[i];
}
else dp1[cnt1++] = a[i];
}
for(int i = id-2;i >= 1;i--)
{
if(a[i] < dp2[cnt2-1])
{
int pos = upper_bound(dp2,dp2+cnt2,a[i]) - dp2;
dp2[pos] = a[i];
}
else dp2[cnt2++] = a[i];
}
printf("%d\n%d\n",cnt2,cnt1);
return 0;
}
🔺187. 导弹防御系统 - AcWing题库(贪心+dp+dfs)
题意:对于每个导弹拦截系统,他拦截的导弹只能单调上升或者单调下降,问最小需要的防御系统的数量。
一次枚举每个数:
先枚举该数在单调上升的子序列中,还是单调下降的字序列中
如果该数在单调上升的序列中,则枚举在哪个单调上升的序列后面。
如果该数被放到了单调下降的序列中,则枚举在那个单调下降的序列后面。
优化:
u p [ ] up[] up[]存储所有上升序列的末尾元素
d o w n [ ] down[] down[]存储所有下降序列的末尾元素
贪心的放到上升序列的恰好比他小的元素后面
同理,贪心的放到下降序列恰好比他大的元素后面
采用迭代加深的方式
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 60;
int n;
int h[MAXN], up[MAXN], down[MAXN];
bool dfs(int depth, int u, int su, int sd)
{
if(su + sd > depth) return false;
if(u > n) return true;
bool flag = false;
// up
for(int i = 1;i <= su;i++)
{
if(h[u] > up[i])
{
int t = up[i];
up[i] = h[u];
if(dfs(depth, u+1, su, sd)) return true;
up[i] = t;
flag = true;
break;
}
}
if(!flag)
{
up[su+1] = h[u];
if(dfs(depth, u+1, su+1, sd)) return true;
}
flag = false;
// down
for(int i = 1;i <= sd;i++)
{
if(h[u] < down[i])
{
int t = down[i];
down[i] = h[u];
if(dfs(depth, u+1, su, sd)) return true;
down[i] = t;
flag = true;
break;
}
}
if(!flag)
{
down[sd+1] = h[u];
if(dfs(depth, u+1, su, sd+1)) return true;
}
return false;
}
int main()
{
while(true)
{
scanf("%d",&n);
if(!n) break;
for(int i = 1;i <= n;i++) scanf("%d", &h[i]);
int depth = 0;
while(!dfs(depth, 1, 0, 0)) depth++;
printf("%d\n",depth);
}
return 0;
}
🔺272. 最长公共上升子序列 - AcWing题库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xs6J5i9C-1629966810412)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210728131817651.png)]
以 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 a [ 1 − i ] , b [ 1 − j ] a[1-i],b[1-j] a[1−i],b[1−j],且以 b [ j ] b[j] b[j]结尾的最长公共上升子序列长度
当最长公共上升子序列不包含 a [ i ] a[i] a[i] d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
当最长公共上升子序列包含 a [ i ] a[i] a[i] 等价于 a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j]
最长上升子序列以 b [ j ] b[j] b[j]结尾 d p [ i ] [ j ] = 1 dp[i][j] = 1 dp[i][j]=1
倒数第二个元素是 b [ 1 ] b[1] b[1]子集 d p [ i ] [ j ] = d p [ i − 1 ] [ 1 ] + 1 dp[i][j] = dp[i-1][1] + 1 dp[i][j]=dp[i−1][1]+1
倒数第二个元素是 b [ j − 1 ] b[j-1] b[j−1]子集 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i−1][j−1]+1
所以初始化 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
如果 a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j] d p [ i ] [ j ] = m a x k = 1 j − 1 d p [ i − 1 ] [ k ] + 1 dp[i][j] = max_{k = 1} ^ {j-1}dp[i-1][k]+1 dp[i][j]=maxk=1j−1dp[i−1][k]+1
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];
if (a[i] == b[j])
{
int maxv = 1;
for (int k = 1; k < j; k ++ )
if (a[i] > b[k])
maxv = max(maxv, f[i - 1][k] + 1);
f[i][j] = max(f[i][j], maxv);
}
}
}
此时需要 O ( n 3 ) O(n^3) O(n3)复杂度,需要进行优化
然后我们发现每次循环求得的
m
a
x
v
maxv
maxv是满足
a
[
i
]
>
b
[
k
]
a[i] > b[k]
a[i]>b[k]的
f
[
i
−
1
]
[
k
]
+
1
f[i - 1][k] + 1
f[i−1][k]+1的前缀最大值。
因此可以直接将
m
a
x
v
maxv
maxv提到第一层循环外面,减少重复计算,此时只剩下两重循环。
O ( n 2 ) O(n^2) O(n2)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int M = 3010;
int n, a[M], b[M], dp[M][M];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int j = 1;j <= n;j++) scanf("%d",&b[j]);
for(int i = 1;i <= n;i++)
{
int res = 1;
for(int j = 1;j <= n;j++)
{
dp[i][j] = dp[i-1][j];
if(a[i] == b[j]) dp[i][j] = res;
if(a[i] > b[j]) res = max(res, dp[i][j] + 1);
}
}
int ans = 1;
for(int i = 1;i <= n;i++) ans = max(ans, dp[n][i]);
printf("%d\n",ans);
return 0;
}
背包模型
完全背包:求所有前缀的最大值
多重背包:求所有滑动窗口的最大值
423. 采药 - AcWing题库 (01背包)
标准01背包
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int M = 100;
int t, m;
int w[M], v[M], dp[1010];
int main()
{
scanf("%d%d",&t,&m);
for(int i = 1;i <= m;i++) scanf("%d%d",&w[i],&v[i]);
for(int i = 1;i <= m;i++)
{
for(int j = t;j >= w[i];j--)
{
dp[j] = max(dp[j], dp[j-w[i]] + v[i]) ;
}
}
int ans = dp[t];
printf("%d\n",ans);
return 0;
}
1024. 装箱问题 - AcWing题库 (01背包改版)
初始化: d p [ n ] dp[n] dp[n]表示体积为n的背包最后剩下的空间
初始化: d p [ i ] = i dp[i] = i dp[i]=i
d p [ n ] = m i n ( d p [ n ] , d p [ n − a [ i ] ] ) dp[n] = min(dp[n], dp[n-a[i]]) dp[n]=min(dp[n],dp[n−a[i]])
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, V;
int a[35], dp[20020];
int main()
{
scanf("%d%d",&V,&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1;i <= V;i++) dp[i] = i;
for(int i = 1;i <= n;i++)
{
for(int j = V;j >= a[i];j--)
{
dp[j] = min(dp[j], dp[j - a[i]]);
}
}
printf("%d\n",dp[V]);
return 0;
}
1022. 宠物小精灵之收服 - AcWing题库 (二维费用背包问题)
花费1:精灵球数量
花费2:皮卡丘体力值
价值:小精灵的数量
状态表示: f [ i , j , k ] f[i,j,k] f[i,j,k]表示只考虑前i个物品,费用1不超过j,费用2不超过k的最大价值
状态转移: f [ i , j , k ] = max ( f [ i − 1 , j , k ] + f [ i − 1 , j − v 1 [ i ] , k − v 2 [ i ] ] + 1 ) f[i,j,k] = \max(f[i-1,j,k] + f[i-1,j-v1[i],k-v2[i]] + 1) f[i,j,k]=max(f[i−1,j,k]+f[i−1,j−v1[i],k−v2[i]]+1)
最多收服的小精灵数量: f [ K , N , M ] f[K,N,M] f[K,N,M]
最小耗费的体力值: m i n ( m ) min (m) min(m) 满足 f [ k , N , m ] = f [ k , N , M ] f[k,N,m] = f[k,N,M] f[k,N,m]=f[k,N,M]
可根据状态转移优化成一维
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, m, k;
int v1[110], v2[110], f[1010][510];
int main()
{
scanf("%d%d%d",&n, &m, &k);
for(int i = 1;i <= k;i++)
{
scanf("%d%d", &v1[i], &v2[i]);
}
for(int i = 1;i <= k;i++)
{
for(int j = n;j >= v1[i];j--)
{
for(int x = m-1;x >= v2[i];x--)
{
f[j][x] = max(f[j][x], f[j-v1[i]][x-v2[i]] + 1);
}
}
}
int y = m;
while(y > 0 && f[n][m-1] == f[n][y-1]) y--;
printf("%d %d\n",f[n][m-1], m - y);
return 0;
}
278. 数字组合 - AcWing题库(完全背包求数量)
看作01背包,容量为和m,每个物品价值 a [ i ] a[i] a[i]
初始化: d p [ 0 ] = 1 dp[0] = 1 dp[0]=1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-et8m1pcj-1629966810414)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210728144904960.png)]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, m, a[110], dp[10010];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
}
dp[0] = 1;
for(int i = 1;i <= n;i++)
{
for(int j = m;j >= a[i];j--)
{
dp[j] += dp[j-a[i]];
}
}
printf("%d\n",dp[m]);
return 0;
}
1023. 买书 - AcWing题库(简单完全背包问题)
统计方案数
dp[0] = 1
dp[j] += dp[j-a[i]]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, a[4] = {10, 20, 50, 100}, dp[1010];
int main()
{
scanf("%d",&n);
dp[0] = 1;
for(int i = 0;i <= 3;i++)
{
for(int j = a[i];j <= n;j++)
{
dp[j] += dp[j - a[i]];
}
}
printf("%d\n",dp[n]);
return 0;
}
1021. 货币系统 - AcWing题库(简单完全背包)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, m;
int a[20];
long long dp[3010];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
dp[0] = 1;
for(int i = 1;i <= n;i++)
{
for(int j = a[i];j <= m;j++)
{
dp[j] += dp[j - a[i]];
}
}
printf("%lld\n", dp[m]);
return 0;
}
532. 货币系统 - AcWing题库(同上)
题意:给n种不同的货币,求问最小的m,使m种货币也能同时表示出n种货币能表示的,不能表示n种货币能表示的。
答案中的系统方案一定是由原先的系统方案去掉若干种货币得到
证明:
令给出的系统中的货币面值为 A 集合,需要得到的货币面值为 B 集合。
引理:A 集合中不能被其他数组成的数一定会在 B 集合中出现。
引理的证明:设有一个数 x∈A 且不能被 A 集合中其他数凑出来。 根据等价,如果 x∉B ,那么 B 中的其他数一定能组成 x .这就说明 B 中至少存在一个不属于 A 集合且不能被 A 组合出来的数(不然 A 集合就一定能合成 x ),那么这个数本身不属于 A 能组成的范畴,却属于 B 能组成的范畴,就不符合题意了。所以 x∈B ,引理正确性证毕。
那么现在我们需要证明:B⊆A.
仍然采用反证法。设存在一个数 x 满足 x∈B 且 x∉A .
根据题意,显然 x 能被 A 中若干个 a1,a2,…,ak组成(假定这些数不能被拆分成 A 中其他的数,如果能拆分就直接拿拆分方案替换即可)。根据引理,这些数都属于 B ,也就是说,B 完全可以通过这些数组成 x ,那么 B 中再存在一个 x 显然就是多余的,和 B 集合最小的要求不符。
那么只需在原先的基础之上去掉某些数
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T, n, a[110], dp[25010];
int main()
{
scanf("%d",&T);
while(T--)
{
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
dp[0] = 1;
scanf("%d",&n);
int maxn = 0;
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
maxn = max(maxn, a[i]);
}
for(int i = 1;i <= n;i++)
{
for(int j = a[i];j <= maxn;j++)
{
dp[j] += dp[j-a[i]];
}
}
int ans = 0;
for(int i = 1;i <= n;i++)
{
if(dp[a[i]] == 1) ans++;
}
printf("%d\n",ans);
}
return 0;
}
🔺6. 多重背包问题 III - AcWing题库(多重背包问题)(单调队列优化DP)
我们令 dp[j] 表示容量为j的情况下,获得的最大价值
那么,针对每一类物品 i ,我们都更新一下 dp[m] --> dp[0] 的值,最后 dp[m] 就是一个全局最优值
dp[m] = max(dp[m], dp[m-v] + w, dp[m-2*v] + 2*w, dp[m-3*v] + 3*w, ...)
接下来,我们把 dp[0] --> dp[m] 写成下面这种形式
dp[0], dp[v], dp[2*v], dp[3*v], ... , dp[k*v]
dp[1], dp[v+1], dp[2*v+1], dp[3*v+1], ... , dp[k*v+1]
dp[2], dp[v+2], dp[2*v+2], dp[3*v+2], ... , dp[k*v+2]
...
dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j]
显而易见,m 一定等于 k*v + j,其中 0 <= j < v
所以,我们可以把 dp 数组分成 j 个类,每一类中的值,都是在同类之间转换得到的
也就是说,dp[k*v+j] 只依赖于 { dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j] }
因为我们需要的是{ dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j] } 中的最大值,
可以通过维护一个单调队列来得到结果。这样的话,问题就变成了 j 个单调队列的问题
所以,我们可以得到
dp[j] = dp[j]
dp[j+v] = max(dp[j] + w, dp[j+v])
dp[j+2v] = max(dp[j] + 2w, dp[j+v] + w, dp[j+2v])
dp[j+3v] = max(dp[j] + 3w, dp[j+v] + 2w, dp[j+2v] + w, dp[j+3v])
...
但是,这个队列中前面的数,每次都会增加一个 w ,所以我们需要做一些转换
dp[j] = dp[j]
dp[j+v] = max(dp[j], dp[j+v] - w) + w
dp[j+2v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w) + 2w
dp[j+3v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w, dp[j+3v] - 3w) + 3w
...
这样,每次入队的值是 dp[j+k*v] - k*w
单调队列问题,最重要的两点
1)维护队列元素的个数,如果不能继续入队,弹出队头元素
2)维护队列的单调性,即:尾值 >= dp[j + k*v] - k*w
本题中,队列中元素的个数应该为 s+1 个,即 0 -- s 个物品 i
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 20010;
int n, m;
int dp[MAXN], tmp[MAXN], q[MAXN];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
{
int v, w, s;
scanf("%d%d%d",&v,&w,&s);
memcpy(tmp, dp, sizeof(dp));
for(int j = 0;j < v;j++)
{
int hh = 0, tt = -1;
for(int k = j;k <= m;k += v)
{
while(hh <= tt && q[hh] < k - s * v) hh++;
while(hh <= tt && tmp[q[tt]] - (q[tt] - j) / v * w <= tmp[k] - (k - j) / v * w) tt--;
q[++tt] = k;
dp[k] = tmp[q[hh]] + (k - q[hh]) / v * w;
}
}
}
printf("%d\n",dp[m]);
return 0;
}
1019. 庆功会 - AcWing题库(同上)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 6010;
int n, m;
int dp[MAXN], tmp[MAXN], q[MAXN];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < n;i++)
{
int v, w, s;
scanf("%d%d%d",&v,&w,&s);
memcpy(tmp, dp, sizeof(dp));
for(int j = 0;j < v;j++)
{
int hh = 0, tt = -1;
for(int k = j;k <= m;k += v)
{
while(hh <= tt && k - s * v > q[hh]) hh++;
while(hh <= tt && tmp[q[tt]] - (q[tt] - j) / v * w <= tmp[k] - (k - j) / v * w) tt--;
q[++tt] = k;
dp[k] = tmp[q[hh]] + (k - q[hh]) / v * w;
}
}
}
printf("%d\n",dp[m]);
return 0;
}
7. 混合背包问题 - AcWing题库(混合背包问题)
01背包看作特殊的多重背包问题(采用二进制优化)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 1010;
int N, V;
int dp[M];
int main()
{
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++)
{
int v, w, s;
scanf("%d%d%d",&v,&w,&s);
if(s == 0)
{
for(int j = v;j <= V;j++)
{
dp[j] = max(dp[j], dp[j - v] + w);
}
}
else
{
if(s == -1) s = 1;
for(int k = 1; k <= s;k *= 2)
{
for(int j = V;j >= k * v;j--)
{
dp[j] = max(dp[j], dp[j - k * v] + k * w);
}
s -= k;
}
if(s)
{
for(int j = V;j >= s * v;j--)
{
dp[j] = max(dp[j], dp[j - s * v] + s * w);
}
}
}
}
printf("%d\n", dp[V]);
return 0;
}
8. 二维费用的背包问题 - AcWing题库(二维费用背包)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 101;
int N, V, M;
int dp[MAXN][MAXN];
int main()
{
scanf("%d%d%d",&N,&V,&M);
for(int i = 1;i <= N;i++)
{
int v, m, w;
scanf("%d%d%d",&v,&m,&w);
for(int j = V;j >= v;j--)
{
for(int k = M;k >= m;k--)
{
dp[j][k] = max(dp[j][k], dp[j - v][k - m] + w);
}
}
}
printf("%d\n", dp[V][M]);
return 0;
}
1020. 潜水员 - AcWing题库
f [ i ] [ j ] f[i][j] f[i][j] 表示氧气体积 ≥ i \ge i ≥i 氮气体积 ≥ j \ge j ≥j的最小瓶子体积。
循环的时候01背包需要枚举到0,然后取max
dp[i][j] = min(dp[i][j], dp[max(0, i - a)][max(0, j - b)] + c);
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 100;
int n, m;
int k;
int dp[MAXN][MAXN];
int main()
{
scanf("%d%d",&m,&n);
scanf("%d",&k);
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
while(k--)
{
int a, b, c;
scanf("%d%d%d",&a,&b,&c);
for(int i = m; i >= 0;i--)
{
for(int j = n;j >= 0;j--)
{
dp[i][j] = min(dp[i][j], dp[max(0, i - a)][max(0, j - b)] + c);
}
}
}
printf("%d\n", dp[m][n]);
return 0;
}
1013. 机器分配 - AcWing题库
方法1:dfs
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int maxx;
int n, m;
int a[12][20], cnt[13], tmp[13];
void dfs(int sum, int num, int res) // dfs设置成void会更好,因为需要记录方案
{
if(num == n)
{
sum += a[num][res];
tmp[num] = res;
if(maxx < sum)
{
maxx = sum;
for(int i = 1;i <= n;i++) cnt[i] = tmp[i];
}
return;
}
for(int i = 0;i <= res;i++) // 循环从0开始
{
tmp[num] = i;
dfs(sum+a[num][i], num + 1, res - i);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
scanf("%d",&a[i][j]);
}
}
dfs(0, 1, m);
printf("%d\n", maxx);
for(int i = 1;i <= n;i++) printf("%d %d\n", i, cnt[i]);
return 0;
}
方法2:分组背包
将公司看作组数n,拿物品的数量为体积,总体积为m。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 12, M = 17;
int n, m;
int a[N][M], way[N], dp[N][M];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
scanf("%d", &a[i][j]);
}
}
for(int i = 1;i <= n;i++)
{
for(int j = 0;j <= m;j++)
{
dp[i][j] = dp[i-1][j];
for(int k = 1;k <= j;k++)
{
dp[i][j] = max(dp[i][j], dp[i-1][j-k] + a[i][k]);
}
}
}
printf("%d\n", dp[n][m]);
int j = m;
for(int i = n;i >= 1;i--)
{
for(int k = 0;k <= j;k++)
{
if(dp[i][j] == dp[i-1][j-k] + a[i][k])
{
way[i] = k;
j -= k;
break;
}
}
}
for(int i = 1;i <= n;i++) printf("%d %d\n", i, way[i]);
return 0;
}
426. 开心的金明 - AcWing题库(简单01背包)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int n, m;
int dp[30005];
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1;i <= m;i++)
{
int v, p;
scanf("%d%d", &v, &p);
for(int j = n;j >= v;j--)
{
dp[j] = max(dp[j], dp[j - v] + v * p);
}
}
printf("%d\n", dp[n]);
return 0;
}
🔺10. 有依赖的背包问题 - AcWing题库(树形dp)
d p [ u ] [ k ] dp[u][k] dp[u][k]表示以u为根节点,体积剩余k的最大价值
p为u的子节点 (采用分组背包,先枚举体积,再枚举决策)
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[p][k])
递归结束再将根节点加进去
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxvtmH5G-1629966810417)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210804111511179.png)]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 105;
struct node{
int to, next;
}N[MAXN];
int head[MAXN], dp[MAXN][MAXN], id, v[MAXN], w[MAXN], p;
int n, V;
inline void addedge(int u, int v)
{
N[id].to = v;
N[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int u)
{
for(int i = head[u]; i != -1; i = N[i].next)
{
int p = N[i].to;
dfs(p);
// 分组背包
for(int j = V - v[u]; j >= 0; j--) // 枚举体积
{
for(int k = 0;k <= j;k++) // 枚举决策
{
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[p][k]);
}
}
}
// 加入物品u
for(int i = V;i >= v[u];i--) dp[u][i] = dp[u][i - v[u]] + w[u];
for(int i = 0;i < v[u];i++) dp[u][i] = 0;
}
int main()
{
scanf("%d%d",&n, &V);
memset(head, -1, sizeof(head));
int root;
for(int i = 1;i <= n;i++)
{
scanf("%d%d%d", &v[i], &w[i], &p);
if(p == -1) root = i;
addedge(p, i);
}
dfs(root);
printf("%d\n", dp[root][V]);
return 0;
}
11. 背包问题求方案数 - AcWing题库)
g [ i ] [ j ] g[i][j] g[i][j] 表示当 f [ i ] [ j ] f[i][j] f[i][j]取到最大值的时候的方案数
if(f[i][j] == f[i-1][j]) g[i][j] = g[i-1][j]
if(f[i][j] == f[i-1][j - v[i]] + w[i]) g[i][j] += g[i-1][j-v[i]]
可以优化成为一维的,只需统计 j = V j = V j=V时, g [ i ] [ j ] g[i][j] g[i][j]的数量
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int MAXN = 1005;
ll N, V, v[MAXN], w[MAXN], dp[MAXN], g[MAXN];
int main()
{
scanf("%d%d", &N, &V);
for(int i = 0;i < N;i++)
{
scanf("%d%d", &v[i], &w[i]);
}
g[0] = 1;
for(int i = 0;i < N;i++)
{
for(int j = V;j >= v[i];j--)
{
ll cnt = 0;
int maxx = max(dp[j], dp[j - v[i]] + w[i]);
if(maxx == dp[j]) cnt = g[j];
if(maxx == dp[j - v[i]] + w[i]) cnt = (cnt + g[j - v[i]]) % mod;
g[j] = cnt;
dp[j] = maxx;
}
}
ll maxn = 0, res = 0;
for(int i = 0;i <= V;i++) maxn = max(maxn, dp[i]);
for(int i = 0;i <= V;i++)
{
if(maxn == dp[i]) res = (res + g[i]) % mod;
}
printf("%lld\n", res);
return 0;
}
12. 背包问题求具体方案 - AcWing题库
逆序更新dp数组,保证最后一次被更新的一定是最小的
贪心选择,从小到大枚举物品,如果当前物品可选可不选,则必须选,能保证字典序最小
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1005;
int N, V;
int dp[MAXN][MAXN], v[MAXN], w[MAXN];
int main()
{
scanf("%d%d", &N, &V);
for(int i = 1;i <= N;i++) scanf("%d%d", &v[i], &w[i]);
for(int i = N;i >= 1;i--) // 从大到小进行枚举,保证最后一次被更新的一定是最小的
{
for(int j = 0;j <= V;j++)
{
dp[i][j] = dp[i + 1][j];
if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i + 1][j - v[i]] + w[i]);
}
}
int j = V;
for(int i = 1;i <= N;i++) // 从小到大寻找物品
{
if(j >= v[i] && dp[i][j] == dp[i + 1][j - v[i]] + w[i])
{
printf("%d ", i);
j -= v[i];
}
}
printf("\n");
return 0;
}
🔺734. 能量石 - AcWing题库(贪心+DP)
题意:n个能量石,吃下去需要一定时间,同时每一秒钟会流失一定能量,问最大能吃的能量是多少
贪心:对于两个相邻要吃的 i , j i,j i,j来说
先i后j: E i + E j − L j ∗ S i E_i+E_j-L_j*S_i Ei+Ej−Lj∗Si
先j后i: E i + E j − L i ∗ S j E_i+E_j-L_i*S_j Ei+Ej−Li∗Sj
能量最大为 L j ∗ S i < L i ∗ S j L_j*S_i<L_i*S_j Lj∗Si<Li∗Sj
所以顺序已定,只需考虑要吃哪些。
DP
吃前i个物品,且时间不超过j
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 , j − s ] + e − ( j − s ) ∗ l ) dp[i][j] = max(dp[i-1][j], dp[i-1,j-s]+e-(j-s)*l) dp[i][j]=max(dp[i−1][j],dp[i−1,j−s]+e−(j−s)∗l)
总时间为所有时间之和。可优化为1维
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 10010;
int T, n;
struct node{
int s, e, l;
bool operator<(const node &a)
{
return s * a.l < l * a.s;
}
}N[MAXN];
int dp[MAXN];
int main()
{
scanf("%d", &T);
for(int x = 1;x <= T;x++)
{
memset(dp,0,sizeof(dp));
int m = 0;
scanf("%d", &n);
for(int i = 1;i <= n;i++)
{
int s, e, l;
scanf("%d%d%d", &s, &e, &l);
N[i] = {s, e, l};
m += s;
}
sort(N+1, N+n+1);
/*
for(int i = 1;i <= n;i++)
{
printf("%d %d %d\n", N[i].s, N[i].e, N[i].l);
}
*/
for(int i = 1;i <= n;i++)
{
for(int j = m;j >= N[i].s;j--)
{
dp[j] = max(dp[j], dp[j - N[i].s] + max(0, N[i].e - (j - N[i].s) * N[i].l));
}
}
int maxn = 0;
for(int i = 1;i <= m;i++) maxn = max(maxn, dp[i]);
printf("Case #%d: %d\n", x, maxn);
}
return 0;
}
🔺487. 金明的预算方案 - AcWing题库(分组背包)
可以将每个主件及其附件看作一个物品组,记主件为p,两个附件为a,b,则最多一共有4种组合:
1.p
2.p,a
3.p,b
4.p,a,b
这四种组合是互斥的,最多只能从中选一种,因此可以将每种组合看作一个物品,那么问题就变成了分组背包问题。
分组背包的时间复杂度是 物品总数 * 总体积,因此总时间复杂度是 O ( N m ) O(Nm) O(Nm)
在枚举四种组合时可以使用二进制的思想,可以简化代码。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int MAXN = 32010;
PII master[MAXN];
vector<PII>servent[MAXN];
int n, m;
int dp[MAXN];
int main()
{
cin >> n >> m;
for(int i = 1;i <= m;i++)
{
int v, p, q;
cin >> v >> p >> q;
if(!q) master[i] = {v, v * p};
else servent[q].push_back({v, v * p});
}
for(int i = 1;i <= m;i++)
{
if(master[i].first)
{
for(int j = n;j >= 0;j--)
{
for(int k = 0;k < 1 << servent[i].size();k++)
{
int v = master[i].first, w = master[i].second;
{
for(int x = 0;x < servent[i].size();x++)
{
if(k >> x & 1)
{
v += servent[i][x].first;
w += servent[i][x].second;
}
}
}
if(j >= v) dp[j] = max(dp[j], dp[j - v] + w);
}
}
}
}
printf("%d\n", dp[n]);
return 0;
}
状态机模型
1049. 大盗阿福 - AcWing题库(简单状态机)
dp[i][0] = max(dp[i - 1][1], dp[i - 1][0]);
dp[i][1] = dp[i - 1][0] + a[i];
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1e5+50;
int T, n;
int a[MAXN], dp[MAXN][2];
int main()
{
cin >> T;
while(T--)
{
scanf("%d", &n);
for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
memset(dp, 0, sizeof(dp));
for(int i = 1;i <= n;i++)
{
dp[i][0] = max(dp[i - 1][1], dp[i - 1][0]);
dp[i][1] = dp[i - 1][0] + a[i];
}
int ans = max(dp[n][0], dp[n][1]);
printf("%d\n", ans);
}
return 0;
}
1057. 股票买卖 IV - AcWing题库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6i3xyFuR-1629966810419)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210813105924320.png)]
关于为什么是第一次买的时候交易次数加一,而不是卖的时候?
终究要回归到状态转移的起点,第一支股票只有买,和不买这两个操作,一定不可能是卖和不卖的这两个操作,因此第一支股票如果买入时,必须按照一次交易处理。否则如果第一次股票如果买入时,不按一次交易处理,也就代表着第一支股票卖出才算一次交易,也就代表着在第一支股票卖出之前还买了一支股票,显然是矛盾的。
记住进行空间优化
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 50;
int n, k;
int a[MAXN], dp[105][2];
int main()
{
scanf("%d%d", &n, &k);
for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
memset(dp, -0x3f, sizeof(dp));
dp[0][0] = 0;
for(int i = 1;i <= n;i++)
{
for(int j = k;j >= 1;j--)
{
dp[j][0] = max(dp[j][0], dp[j][1] + a[i]);
dp[j][1] = max(dp[j][1], dp[j - 1][0] - a[i]);
}
}
int maxn = dp[0][0];
for(int i = 0;i <= k;i++) maxn = max(maxn, dp[i][0]);
printf("%d\n", maxn);
return 0;
}
1058. 股票买卖 V - AcWing题库
d p [ i ] [ j ] dp[i][j] dp[i][j]为第 i i i天,状态为 j j j的情况
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]表示空仓情况:dp[i][0] = max(dp[i-1][2], dp[i-1][0])
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]表示手中有股票:dp[i][1] = max(dp[i-1][1], dp[i-1][0] - a[i])
d
p
[
i
]
[
2
]
dp[i][2]
dp[i][2]表示冷冻期:dp[i][2] = dp[i-1][1] + a[i]
初始化: d p [ i ] [ j ] = − i n f , d p [ 0 ] [ 0 ] = 0 dp[i][j]=-inf,dp[0][0] = 0 dp[i][j]=−inf,dp[0][0]=0
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace st
typedef long long ll;
const int MAXN = 1e5+50;
int n;
ll a[MAXN], dp[MAXN][3];
int main()
{
scanf("%d", &n);
for(int i = 1;i <= n;i++) scanf("%lld", &a[i]);
memset(dp, -0x3f, sizeof(dp));
dp[0][0] = 0;
for(int i = 1;i <= n;i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][2]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - a[i]);
dp[i][2] = dp[i - 1][1] + a[i];
}
printf("%lld\n", max(dp[n][0], dp[n][2]));
return 0;
}
状压DP
1064. 小国王 - AcWing题库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rYtBZ7ns-1629966810420)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210813191820295.png)]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int M = 1 << 10, N = 13, K = 110;
int n, k;
int cnt[M];
vector<int>head[M];
vector<int>state;
ll f[N][K][M];
bool check(int x)
{
for(int i = 0;i < n+1;i++)
{
if(x >> i & 1 && x >> (i + 1) & 1) return false;
}
return true;
}
int count(int x)
{
int res = 0;
for(int i = 0;i < n+1;i++)
{
if(x >> i & 1) res++;
}
return res;
}
int main()
{
scanf("%d%d", &n, &k);
for(int i = 0;i < 1 << n;i++)
{
if(check(i))
{
state.push_back(i);
cnt[i] = count(i);
}
}
for(int i = 0;i < state.size();i++)
{
for(int j = 0;j < state.size();j++)
{
int a = state[i], b = state[j];
if((a & b) == 0 && check(a | b)) head[i].push_back(j);
}
}
f[0][0][0] = 1;
for(int i = 1;i <= n+1;i++)
{
for(int j = 0;j <= k;j++)
{
for(int a = 0;a < state.size();a++)
{
for(auto b : head[a])
{
int c = cnt[state[a]];
if(j >= c) f[i][j][a] += f[i - 1][j - c][b];
}
}
}
}
printf("%lld\n", f[n+1][k][0]);
return 0;
}
327. 玉米田 - AcWing题库
dp[i][j] += dp[i-1][k]
需要判断当前能否种玉米,不能有两个连续的1,上下满足(a & b) == 0
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int mod = 1e8, M = 13;
int n, m;
int a[M][M];
ll dp[M][1 << M];
vector<int>state;
vector<int>head[1 << M];
bool check(int x)
{
for(int i = 0;i < n;i++)
{
if(x >> i & 1 && x >> (i + 1) & 1) return false;
}
return true;
}
bool checka(int x, int m)
{
for(int i = 1;i <= n;i++)
{
if(a[m][i] == 0 && x >> (n - i) & 1) return false;
}
return true;
}
int main()
{
scanf("%d%d", &m, &n);
for(int i = 1;i <= m;i++)
{
for(int j = 1;j <= n;j++)
{
scanf("%d", &a[i][j]);
}
}
for(int i = 0;i < 1 << n;i++)
{
if(check(i))
{
state.push_back(i);
}
}
for(int i = 0;i < state.size();i++)
{
for(int j = 0;j < state.size();j++)
{
int a = state[i], b = state[j];
if((a & b) == 0) head[i].push_back(j);
}
}
dp[0][0] = 1;
for(int i = 1;i <= m;i++)
{
for(int j = 0;j < state.size();j++)
{
for(auto k : head[j])
{
if(checka(state[j], i)) dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;
}
}
}
ll ans = 0;
for(int i = 0;i < 1 << n;i++) ans = (ans + dp[m][i]) % mod;
printf("%lld\n", ans);
return 0;
}
292. 炮兵阵地 - AcWing题库
dp[i][j][k]
表示第i行,状态为j,第
i
−
1
i-1
i−1行状态为k
dp[i][j][k] = max(dp[i][j][k], dp[i-1][k][x] + cnt(第i行状态为j,1的数量))
满足连续三行状态 a , b , c a,b,c a,b,c两两相与都为0,并且高地上不能放
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 105, M = 15, MAXN = 1 << 10;
int n, m;
int cnt[MAXN];
ll dp[2][MAXN][MAXN];
vector<int>state;
char s[N][M];
int count(int x)
{
int res = 0;
for(int i = 0;i < m;i++)
{
if(x >> i & 1) res++;
}
return res;
}
bool check(int x)
{
for(int i = 0;i < m;i++)
{
if(x >> i & 1 && (x >> (i + 1) & 1 || x >> (i + 2) & 1)) return false;
}
return true;
}
bool checka(int x, int n)
{
for(int i = 0;i < m;i++)
{
if(s[n][m-i] == 'H' && x >> i & 1) return false;
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
char c;
cin >> c; // 注意采用cin读入,用getchar可能没法忽略空格
s[i][j] = c;
}
}
for(int i = 0;i < 1 << m;i++)
{
if(check(i))
{
state.push_back(i);
cnt[i] = count(i);
}
}
for(int i = 1;i <= n;i++)
{
for(int j = 0;j < state.size();j++)
{
for(int k = 0;k < state.size();k++)
{
for(int x = 0; x < state.size();x++)
{
int a = state[j], b = state[k], c = state[x];
if((a & b) || (b & c) || (a & c)) continue;
if(!checka(a, i)) continue;
dp[i & 1][j][k] = max(dp[i & 1][j][k], dp[(i - 1) & 1][k][x] + cnt[a]);
}
}
}
}
ll res = 0;
for(int i = 0;i < state.size();i++)
{
for(int j = 0;j < state.size();j++)
{
res = max(res, dp[n & 1][i][j]);
}
}
printf("%lld\n", res);
return 0;
}
524. 愤怒的小鸟 - AcWing题库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCpev2f6-1629966810420)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210816114734470.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpC97xP3-1629966810421)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210816114855394.png)]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
typedef pair<double, double> PDD;
const int M = 20;
const double eps = 1e-7;
int T, n, m;
PDD state[M];
int f[1 << M];
int path[M][M];
int cmp(double x, double y)
{
if(fabs(x - y) < eps) return 0;
else if(x > y) return 1;
else return -1;
}
void init()
{
memset(path, 0, sizeof(path));
for(int i = 0;i < n;i++)
{
path[i][i] = 1 << i;
for(int j = 0;j < n;j++)
{
double x1 = state[i].first, y1 = state[i].second;
double x2 = state[j].first, y2 = state[j].second;
if(cmp(x1, x2) == 0) continue;
double a = (y1 * x2 - y2 * x1) / (x1 * x2 *(x1 - x2));
double b = (y1 / x1) - a * x1;
if(cmp(a, 0.0) >= 0) continue;
for(int k = 0;k < n;k++)
{
double x = state[k].first, y = state[k].second;
if(cmp(y, a * x * x + b * x) == 0) path[i][j] += 1 << k;
}
}
}
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
for(int i = 0;i < n;i++)
{
double x, y;
scanf("%lf%lf", &x, &y);
state[i].first = x;
state[i].second = y;
}
memset(f, 0x3f, sizeof(f));
f[0] = 0;
init();
for(int i = 0;i < (1 << n);i++)
{
int t = -1;
for(int j = 0;j < n;j++)
{
if((i >> j & 1) == 0) t = j;
}
if(t != -1)
{
for(int j = 0;j < n;j++)
{
f[i | path[t][j]] = min(f[i | path[t][j]], f[i] + 1);
}
}
}
printf("%d\n", f[(1 << n) - 1]);
}
return 0;
}
区间DP
代码实现
迭代式:
// 先枚举长度,再枚举左端点,求出右端点
for(int len = 1;len <= n;len++)
{
for(int l = 1;l + len - 1 <= n;i++)
{
int r = l + len - 1;
}
}
记忆化搜索:
1068. 环形石子合并 - AcWing题库(环转化为区间)
环转化为区间:
- 方法一:枚举缺口,拉成直线,一共n种情况。
- 方法二:答案最终为n条长度为n的链的所有情况,将区间拉成 2 n 2n 2n,每个区间为 [ i , i + n − 1 ] [i,i+n-1] [i,i+n−1]
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int M = 220;
int n;
int a[2*M], f[2*M][2*M], g[2*M][2*M], sum[2 * M];
int main()
{
scanf("%d", &n);
for(int i = 1;i <= n;i++)
{
scanf("%d", &a[i]);
a[n + i] = a[i];
}
memset(f, 0x3f, sizeof(f));
memset(g, -0x3f, sizeof(g));
for(int i = 1;i <= 2 * n;i++)
{
sum[i] = sum[i - 1] + a[i];
}
for(int len = 1;len <= 2 * n;len++)
{
for(int l = 1;l + len - 1 <= 2 * n;l++)
{
int r = l + len - 1;
if(l == r) {
f[l][r] = 0;
g[l][r] = 0;
continue;
}
for(int k = l;k <= r;k++)
{
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + (sum[r] - sum[l - 1]));
g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + (sum[r] - sum[l - 1]));
}
}
}
int minn = 1e9, maxx = 0;
for(int i = 1;i <= n;i++)
{
minn = min(minn, f[i][i + n - 1]);
maxx = max(maxx, g[i][i + n - 1]);
}
printf("%d\n%d\n", minn, maxx);
return 0;
}
320. 能量项链 - AcWing题库
环形开两倍长度转化为线性的
f[l][r] = f[l][k] + f[k][r] + a[l] * a[k] * a[r] l < k < r
答案为 m a x ( f [ i ] [ i + n ] ) max(f[i][i+n]) max(f[i][i+n])
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 220;
int n;
int a[M], f[M][M];
int main()
{
scanf("%d", &n);
for(int i = 1;i <= n;i++)
{
scanf("%d", &a[i]);
a[n + i] = a[i];
}
for(int len = 3;len <= 2 * n;len++)
{
for(int l = 1;l + len - 1 <= 2 * n;l++)
{
int r = l + len - 1;
for(int k = l + 1;k < r;k++)
{
f[l][r] = max(f[l][r], f[l][k] + f[k][r] + a[l] * a[k] * a[r]);
}
}
}
int maxx = 0;
for(int i = 1;i <= n;i++)
{
maxx = max(maxx, f[i][i + n]);
}
printf("%d\n", maxx);
return 0;
}
479. 加分二叉树 - AcWing题库(dfs+区间dp)
f[l][r] = left * right + a[r]
if(l == r) ans = a[l]
if(k == l) left = 1 else left = f[l][k - 1]
if(k == r) right = 1 else right = f[k + 1][r]
同时记住每次更新的根,dfs进行前序遍历
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 32;
int n;
int a[M];
unsigned f[M][M];
int root[M][M];
void dfs(int l, int r)
{
if(l > r) return;
int k = root[l][r];
printf("%d ", k);
dfs(l, k - 1);
dfs(k + 1, r);
}
int main()
{
scanf("%d", &n);
for(int i = 1;i <= n;i++) {
scanf("%d", &a[i]);
}
for(int len = 1;len <= n;len++)
{
for(int l = 1;l + len - 1 <= n;l++)
{
int r = l + len - 1;
for(int k = l;k <= r;k++)
{
int left = k == l ? 1 : f[l][k - 1];
int right = k == r ? 1 : f[k + 1][r];
int ans = left * right + a[k];
if(l == r) ans = a[l];
if(f[l][r] < ans)
{
f[l][r] = ans;
root[l][r] = k;
}
}
}
}
printf("%d\n", f[1][n]);
dfs(1, n);
printf("\n");
return 0;
}
1069. 凸多边形的划分 - AcWing题库(三角剖分)
f[i][j] = min(f[i][k] + f[k][j] + a[i] * a[k] * a[j]) l < k < r
答案为: f [ 1 ] [ n ] f[1][n] f[1][n]
高精度
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 55, M = 35, INF = 1e9;
int n;
int w[N];
LL f[N][N][M];
void add(LL a[], LL b[])
{
static LL c[M];
memset(c, 0, sizeof c);
for (int i = 0, t = 0; i < M; i ++ )
{
t += a[i] + b[i];
c[i] = t % 10;
t /= 10;
}
memcpy(a, c, sizeof c);
}
void mul(LL a[], LL b)
{
static LL c[M];
memset(c, 0, sizeof c);
LL t = 0;
for (int i = 0; i < M; i ++ )
{
t += a[i] * b;
c[i] = t % 10;
t /= 10;
}
memcpy(a, c, sizeof c);
}
int cmp(LL a[], LL b[])
{
for (int i = M - 1; i >= 0; i -- )
if (a[i] > b[i]) return 1;
else if (a[i] < b[i]) return -1;
return 0;
}
void print(LL a[])
{
int k = M - 1;
while (k && !a[k]) k -- ;
while (k >= 0) cout << a[k -- ];
cout << endl;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> w[i];
LL temp[M];
for (int len = 3; len <= n; len ++ )
for (int l = 1; l + len - 1 <= n; l ++ )
{
int r = l + len - 1;
f[l][r][M - 1] = 1;
for (int k = l + 1; k < r; k ++ )
{
memset(temp, 0, sizeof temp);
temp[0] = w[l];
mul(temp, w[k]);
mul(temp, w[r]);
add(temp, f[l][k]);
add(temp, f[k][r]);
if (cmp(f[l][r], temp) > 0)
memcpy(f[l][r], temp, sizeof temp);
}
}
print(f[1][n]);
return 0;
}
321. 棋盘分割 - AcWing题库
题意:将8*8的棋盘进行分割,使得分割n次的方差最小,每次分割只能在原棋盘割下一块矩形的剩下部分进行分割。
σ
2
=
1
n
∑
i
=
1
n
(
x
i
−
x
)
2
σ
2
=
1
n
∑
i
=
1
n
(
x
i
2
−
2
x
i
x
+
x
2
)
σ
2
=
1
n
∑
i
=
1
n
(
x
i
2
−
x
2
)
\sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i-x)^2 \\ \sigma^2 = \frac{1}{n} \sum_{i=1}^{n}(x_i^2 -2 x_ix+x^2) \\ \sigma^2 = \frac{1}{n} \sum_{i=1}^{n}(x_i^2-x^2)
σ2=n1i=1∑n(xi−x)2σ2=n1i=1∑n(xi2−2xix+x2)σ2=n1i=1∑n(xi2−x2)
为了使
σ
\sigma
σ越小,只需要让每一块的平方和越小
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int M = 9, N = 15;
int n;
int s[M][M];
double f[M][M][M][M][N];
double X;
int sum(int x1, int y1, int x2, int y2)
{
int ans = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
return ans;
}
double get_sum(int x1, int y1, int x2, int y2)
{
double res = (double)sum(x1, y1, x2, y2) - X;
return res * res / n;
}
double dp(int x1, int y1, int x2, int y2, int k)
{
auto &u = f[x1][y1][x2][y2][k];
if(u >= 0) return u;
if(k == 1) return u = get_sum(x1, y1, x2, y2);
u = 1e9;
for(int a = x1; a < x2; a++)
{
u = min(u, get_sum(x1, y1, a, y2) + dp(a + 1, y1, x2, y2, k - 1));
u = min(u, get_sum(a + 1, y1, x2, y2) + dp(x1, y1, a, y2, k - 1));
}
for(int b = y1; b < y2; b++)
{
u = min(u, get_sum(x1, y1, x2, b) + dp(x1, b + 1, x2, y2, k - 1));
u = min(u, get_sum(x1, b + 1, x2, y2) + dp(x1, y1, x2, b, k - 1));
}
return u;
}
int main()
{
scanf("%d", &n);
for(int i = 1;i <= 8;i++)
{
for(int j = 1;j <= 8;j++)
{
scanf("%d", &s[i][j]);
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
}
X = (double)s[8][8] / n;
memset(f, -1, sizeof(f));
double ans = sqrt(dp(1, 1, 8, 8, n));
printf("%.3f\n", ans);
return 0;
}
树形DP
1072. 树的最长路径 - AcWing题库(模板题)
f [ u ] = f [ v ] v ∈ s o n [ u ] + w [ u , v ] f[u] = f[v]_{v ∈ son[u]}+w[u,v] f[u]=f[v]v∈son[u]+w[u,v]
答案为两条最长链之和(用dfs去写)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 10010, M = 10010 * 2;
struct Edge{
int to, next, w;
}E[M];
int head[N];
int id, n, ans;
void inline addedge(int u, int v, int w)
{
E[id].to = v;
E[id].next = head[u];
E[id].w = w;
head[u] = id++;
return ;
}
int dfs(int cur, int father)
{
int dist = 0, d1 = 0, d2 = 0;
for(int i = head[cur];i != -1;i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == father) continue;
int d = dfs(p, cur) + w;
dist = max(dist, d);
if(d >= d1) {
d2 = d1;
d1 = d;
}
else if(d > d2) {
d2 = d;
}
}
ans = max(ans, d1 + d2);
return dist;
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1;i < n;i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c);
addedge(b, a, c);
}
dfs(1, -1);
printf("%d\n", ans);
return 0;
}
1073. 树的中心 - AcWing题库
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 20020, N = 10010;
int head[N], d1[N], d2[N], up[N], p1[N], id, n;
bool is_leaf[N];
struct Edge{
int to, next, w;
}E[M];
inline void addedge(int u, int v, int w)
{
E[id].to = v;
E[id].w = w;
E[id].next = head[u];
head[u] = id++;
return;
}
int dfs_d(int cur, int father) // 向下走保存向下的最大和次大
{
d1[cur] = -1e9, d2[cur] = -1e9;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == father) continue;
int d = dfs_d(p, cur) + w;
if(d >= d1[cur]) {
d2[cur] = d1[cur];
d1[cur] = d;
p1[cur] = p;
}
else if(d > d2[cur]) d2[cur] = d;
}
if(d1[cur] == -1e9) {
is_leaf[cur] = true;
d1[cur] = d2[cur] = 0;
}
return d1[cur];
}
void dfs_u(int cur, int father) // 向上走找父节点的的(最大/次大),或者再往上走
{
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == father) continue;
if(p1[cur] == p) up[p] = max(up[cur], d2[cur]) + w;
else up[p] = max(up[cur], d1[cur]) + w;
dfs_u(p, cur);
}
}
int main()
{
memset(head, -1, sizeof(head));
memset(is_leaf, false, sizeof(is_leaf));
scanf("%d", &n);
for(int i = 1;i < n;i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c);
addedge(b, a, c);
}
dfs_d(1, -1);
dfs_u(1, -1);
int ans = d1[1];
for(int i = 1;i <= n;i++)
{
if(is_leaf[i]) ans = min(ans, up[i]);
else ans = min(ans, max(up[i], d1[i]));
}
printf("%d\n", ans);
return 0;
}
1075. 数字转换 - AcWing题库
一个数它的约数之和小于它的话,将其连一条无向边
每次的终点都是1
即只需要找出从一开始找出两条最长的链(树形dp模板题)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 50005, M = 2 * N;
int n, id, ans;
int head[N];
bool st[N];
struct Edge{
int to, next;
}E[M];
int getsum(int x)
{
if(x == 1) return -1;
int res = 1;
for(int i = 2;i * i <= x;i++)
{
if(x % i == 0)
{
res += i;
if(i * i != x) res += (x / i);
}
}
if(res < x) return res;
else return -1;
}
inline void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return ;
}
int dfs(int cur, int father)
{
int dist = 0, d1 = 0, d2 = 0;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == father) continue;
int d = dfs(p, cur) + 1;
dist = max(dist, d);
if(d >= d1) {
d2 = d1;
d1 = d;
}
else if(d > d2) d2 = d;
ans = max(ans, d1 + d2);
}
return dist;
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1;i <= n;i++)
{
int tmp = getsum(i);
if(tmp != -1) {
addedge(i, tmp);
addedge(tmp, i);
}
}
dfs(1, -1);
printf("%d\n", ans);
return 0;
}
1074. 二叉苹果树 - AcWing题库(有依赖的背包类树形dp)
总结:
一:状态表示:
考虑根节点和孩子之间的关系, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以 i i i为根分配 j j j那么大的体积的最大价值
二:题型分类:
1:边作为体积的写法:
https://www.acwing.com/problem/content/1076/
void dfs(int u, int fa){
for(int i = h[u];~i;i = ne[i]){
int k = v[i];
if(k == fa) continue;
dfs(k, u);
for(int j = m;j >= 0;--j)
for(int t = 0;t <= j-1;++t)
dp[u][j] = max(dp[u][j], dp[u][j-t-1] + dp[k][t] + w[i]);
}
}
2:顶点的数量或者顶点的权值作为体积:
https://www.acwing.com/problem/content/288/
void dfs(int u){
for(int i = h[u];~i;i = ne[i]){//物品组
int k = v[i];
dfs(k);
for(int j = m-1;j >= 0;--j)//背包容量
for(int t = 1;t <= j;++t)//决策组内的选择
dp[u][j] = max(dp[u][j], dp[u][j-t] + dp[k][t]);
}
for(int j = m;j >= 0;--j) dp[u][j] = dp[u][j-1] + w[u];
}
https://www.acwing.com/problem/content/10/
void dfs(int u){
for(int i = h[u];~i;i = ne[i]){
int k = e[i];
dfs(k);
for(int j = m-v[u];j >= 0;--j)
for(int t = 1;t <= j;++t)
dp[u][j] = max(dp[u][j], dp[u][j-t] + dp[k][t]);
}
//j 从m到0
for(int j = m;j >= v[u];--j) dp[u][j] = dp[u][j-v[u]] + w[u];
for(int j = 0;j < v[u];++j) dp[u][j] = 0;
}
题意:给定一颗无向树,带有边权,求边数为q的权值最大值。
分组背包思想:
1.枚举物品组
2.枚举体积
3.枚举决策
依赖关系: i , j , k i,j,k i,j,k依次是父子结点的关系。如果 ( i , j ) (i,j) (i,j)满足要求,则 ( j , k ) (j,k) (j,k)一定满足要求
疑问:为什么 f [ u ] [ j − k − 1 ] f[u][j - k - 1] f[u][j−k−1] 和 f [ e [ i ] ] [ k ] f[e[i]][k] f[e[i]][k] 不会选到相同的边呢,这是因为这是经过优化过的等式,原式为:
f [ u ] [ i ] [ j ] = m a x ( f [ u ] [ i ] [ j ] , f [ u ] [ i − 1 ] [ j − k − 1 ] + f [ e [ i ] ] [ s [ e [ i ] ] ] [ k ] + w [ i ] ) f[u][i][j] = max(f[u][i][j], f[u][i - 1][j - k - 1] + f[e[i]][s[e[i]]][k] + w[i]) f[u][i][j]=max(f[u][i][j],f[u][i−1][j−k−1]+f[e[i]][s[e[i]]][k]+w[i]) s [ e [ i ] ] s[e[i]] s[e[i]]为 e [ i ] e[i] e[i]子树的数量
可以看到 f [ u ] [ i − 1 ] [ j − k − 1 ] f[u][i - 1][j - k - 1] f[u][i−1][j−k−1]只在前 i − 1 i-1 i−1棵子树中找,还没到第 i i i棵子树,所以两个集合是完全不重合的
通过体积逆向枚举滚动数组优化成二维
f [ i ] [ j ] f[i][j] f[i][j]为从i的子树中选边数为j的最大权值
f[i][j] = max(f[i][j], f[i][k] + f[son][j - k - 1] + w[i,son])
选中当前son为根的 j − k − 1 j-k-1 j−k−1条边,给其他子树分配 k k k条边,还有一条 i − s o n i-son i−son的边
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110, M = 220;
int n, q, id, ans;
int head[N], f[N][N];
bool st[N];
struct Edge{
int to, next, w;
}E[M];
inline void addedge(int u, int v, int w)
{
E[id].to = v;
E[id].w = w;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int cur, int father)
{
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(father == p) continue;
dfs(p, cur);
for(int j = q; j; j--) // 循环体积
{
for(int k = 0;k < j;k++) // 循环决策
{
f[cur][j] = max(f[cur][j], f[p][k] + f[cur][j - k - 1] + w);
}
}
}
}
int main()
{
memset(head, -1, sizeof(head));
memset(st, false, sizeof(st));
scanf("%d%d", &n, &q);
for(int i = 1;i < n;i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c);
addedge(b, a, c);
}
dfs(1, -1);
printf("%d\n", f[1][q]);
return 0;
}
323. 战略游戏 - AcWing题库
树形DP基本题:(士兵可以管到与他相邻所有边,因此一条道路上的两个点必有一个被占领)
f [ i ] [ 0 ] f[i][0] f[i][0]表示当前结点不放士兵
f [ i ] [ 1 ] f[i][1] f[i][1]表示当前节点放士兵
f [ c u r ] [ 0 ] + = f [ s o n ] [ 1 ] f[cur][0] += f[son][1] f[cur][0]+=f[son][1]
f [ c u r ] [ 1 ] + = m i n ( f [ s o n ] [ 0 ] , f [ s o n ] [ 1 ] ) f[cur][1] += min(f[son][0], f[son][1]) f[cur][1]+=min(f[son][0],f[son][1])
从root开始dfs
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, id;
const int N = 1510, M = 3030;
int head[N], f[N][2];
bool isnt_root[N];
struct Edge{
int to, next;
}E[M];
inline void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs(int cur)
{
f[cur][0] = 0, f[cur][1] = 1;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
dfs(p);
f[cur][0] += f[p][1];
f[cur][1] += min(f[p][0], f[p][1]);
}
}
int main()
{
while(cin >> n)
{
memset(head, -1, sizeof(head));
memset(isnt_root, false, sizeof(isnt_root));
id = 0;
for(int i = 0;i < n;i++)
{
int v, num;
scanf("%d:(%d)", &v, &num);
for(int j = 0;j < num;j++)
{
int u;
scanf("%d", &u);
addedge(v, u);
isnt_root[u] = true;
}
}
int root = -1;
for(int i = 0;i <= n - 1;i++)
{
if(!isnt_root[i]) root = i;
}
dfs(root);
int ans = min(f[root][0], f[root][1]);
printf("%d\n", ans);
}
return 0;
}
1077. 皇宫看守 - AcWing题库
题意:一个结点可以管到相邻所有结点,问所占结点最少花费
与上一题不同的是,这一题所管理的是点,因此具有三种状态
状态表示:
不放守卫
1.父结点放了,记 f [ u ] [ 0 ] f[u][0] f[u][0]
2.子节点放了,记 f [ u ] [ 1 ] f[u][1] f[u][1]
放置守卫
记 f [ u ] [ 2 ] f[u][2] f[u][2]其中u为当前的结点
这样就可以涵盖整个状态空间
状态转移:
$f[u][0]=∑min(f[j][1],f[j][2]) $。
含义:当前结点u不放且被父节点看到,那么子结点 j j j只能放或者被其子结点看到。因为 u u u不放所以不能拿 f [ j ] [ 0 ] f[j][0] f[j][0]来更新
f [ u ] [ 1 ] = f [ k ] [ 2 ] + ∑ m i n ( f [ j ] [ 1 ] , f [ j ] [ 2 ] ) f[u][1]=f[k][2]+∑min(f[j][1],f[j][2]) f[u][1]=f[k][2]+∑min(f[j][1],f[j][2])。
含义:当前结点u不放,u的子结点k放了,u的其他子结点j放( f [ j ] [ 2 ] f[j][2] f[j][2])或者不放(即 f [ j ] [ 1 ] f[j][1] f[j][1])。没有 f [ j ] [ 0 ] f[j][0] f[j][0]的原因也是因为u不放,且需要枚举k。
f [ u ] [ 2 ] = ∑ m i n ( f [ j ] [ 0 ] , f [ j ] [ 1 ] , f [ j ] [ 2 ] ) f[u][2]=∑min(f[j][0],f[j][1],f[j][2]) f[u][2]=∑min(f[j][0],f[j][1],f[j][2])。
含义:当前结点放了,那么子结点取哪种状态都可以
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1510, M = 3030;
bool vis[N];
int head[N], w[N], f[N][3], id, n;
struct Edge{
int to, next;
}E[M];
inline void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int cur)
{
f[cur][2] = w[cur];
int sum = 0;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
dfs(p);
f[cur][0] += min(f[p][1], f[p][2]);
f[cur][2] += min(f[p][0], min(f[p][1], f[p][2]));
sum += min(f[p][1], f[p][2]);
//依次假设每个结点放, sum - min(f[p][1], f[p][2])就是其他所有结点的放和不放最小值之和
}
f[cur][1] = 1e9;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
f[cur][1] = min(f[cur][1], sum - min(f[p][1],f[p][2]) + f[p][2]);
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1;i <= n;i++)
{
int u, num, idx;
scanf("%d%d%d", &u, &idx, &num);
w[u] = idx;
for(int j = 0; j < num; j++)
{
int v;
scanf("%d", &v);
vis[v] = true;
addedge(u, v);
}
}
int root = -1;
for(int i = 1;i <= n;i++)
{
if(!vis[i]) {
root = i;
break;
}
}
dfs(root);
printf("%d\n", min(f[root][1], f[root][2]));
return 0;
}
root开始dfs
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, id;
const int N = 1510, M = 3030;
int head[N], f[N][2];
bool isnt_root[N];
struct Edge{
int to, next;
}E[M];
inline void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs(int cur)
{
f[cur][0] = 0, f[cur][1] = 1;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
dfs(p);
f[cur][0] += f[p][1];
f[cur][1] += min(f[p][0], f[p][1]);
}
}
int main()
{
while(cin >> n)
{
memset(head, -1, sizeof(head));
memset(isnt_root, false, sizeof(isnt_root));
id = 0;
for(int i = 0;i < n;i++)
{
int v, num;
scanf("%d:(%d)", &v, &num);
for(int j = 0;j < num;j++)
{
int u;
scanf("%d", &u);
addedge(v, u);
isnt_root[u] = true;
}
}
int root = -1;
for(int i = 0;i <= n - 1;i++)
{
if(!isnt_root[i]) root = i;
}
dfs(root);
int ans = min(f[root][0], f[root][1]);
printf("%d\n", ans);
}
return 0;
}
1077. 皇宫看守 - AcWing题库
题意:一个结点可以管到相邻所有结点,问所占结点最少花费
与上一题不同的是,这一题所管理的是点,因此具有三种状态
状态表示:
不放守卫
1.父结点放了,记 f [ u ] [ 0 ] f[u][0] f[u][0]
2.子节点放了,记 f [ u ] [ 1 ] f[u][1] f[u][1]
放置守卫
记 f [ u ] [ 2 ] f[u][2] f[u][2]其中u为当前的结点
这样就可以涵盖整个状态空间
状态转移:
$f[u][0]=∑min(f[j][1],f[j][2]) $。
含义:当前结点u不放且被父节点看到,那么子结点 j j j只能放或者被其子结点看到。因为 u u u不放所以不能拿 f [ j ] [ 0 ] f[j][0] f[j][0]来更新
f [ u ] [ 1 ] = f [ k ] [ 2 ] + ∑ m i n ( f [ j ] [ 1 ] , f [ j ] [ 2 ] ) f[u][1]=f[k][2]+∑min(f[j][1],f[j][2]) f[u][1]=f[k][2]+∑min(f[j][1],f[j][2])。
含义:当前结点u不放,u的子结点k放了,u的其他子结点j放( f [ j ] [ 2 ] f[j][2] f[j][2])或者不放(即 f [ j ] [ 1 ] f[j][1] f[j][1])。没有 f [ j ] [ 0 ] f[j][0] f[j][0]的原因也是因为u不放,且需要枚举k。
f [ u ] [ 2 ] = ∑ m i n ( f [ j ] [ 0 ] , f [ j ] [ 1 ] , f [ j ] [ 2 ] ) f[u][2]=∑min(f[j][0],f[j][1],f[j][2]) f[u][2]=∑min(f[j][0],f[j][1],f[j][2])。
含义:当前结点放了,那么子结点取哪种状态都可以
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1510, M = 3030;
bool vis[N];
int head[N], w[N], f[N][3], id, n;
struct Edge{
int to, next;
}E[M];
inline void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int cur)
{
f[cur][2] = w[cur];
int sum = 0;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
dfs(p);
f[cur][0] += min(f[p][1], f[p][2]);
f[cur][2] += min(f[p][0], min(f[p][1], f[p][2]));
sum += min(f[p][1], f[p][2]);
//依次假设每个结点放, sum - min(f[p][1], f[p][2])就是其他所有结点的放和不放最小值之和
}
f[cur][1] = 1e9;
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
f[cur][1] = min(f[cur][1], sum - min(f[p][1],f[p][2]) + f[p][2]);
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1;i <= n;i++)
{
int u, num, idx;
scanf("%d%d%d", &u, &idx, &num);
w[u] = idx;
for(int j = 0; j < num; j++)
{
int v;
scanf("%d", &v);
vis[v] = true;
addedge(u, v);
}
}
int root = -1;
for(int i = 1;i <= n;i++)
{
if(!vis[i]) {
root = i;
break;
}
}
dfs(root);
printf("%d\n", min(f[root][1], f[root][2]));
return 0;
}