几道板子题——换根DP
板子题1——牛客
板子题2——牛客
求某个点到所有点的深度之和最大?
换根DP的核心就是O(1)的时间把之前的结果状态,转移到当前这个点的结果状态。
比如这个题:根 u 向下走一次到 j 结点,相当于下边的深度少了
s
i
z
e
[
j
]
size[j]
size[j],上边深度多了
n
−
s
i
z
e
[
j
]
n -size[j]
n−size[j]。
所以两个结果状态的差值就是
n
−
2
∗
s
i
z
e
[
j
]
n - 2*size[j]
n−2∗size[j]。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6 + 10;
int ne[N], e[N], w[N], h[N], high[N], f[N], n, m, idx, dp[N];
void init(int u, int father)
{
dp[u] = 1;
high[u] = high[father] + 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father) continue;
init(j, u);
dp[u] += dp[j];
}
}
void dfs(int u, int father)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father) continue;
f[j] = f[u] + n - 2 * dp[j];
dfs(j, u);
}
}
void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
signed main() {
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i <= n - 1; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
init(1, -1);
for (int i = 1; i <= n; i++) f[1] += high[i];
dfs(1, -1);
int ans = -1, k;
for (int i = 1; i <= n; i++)
{
if (ans < f[i])
{
ans = f[i];
k = i;
}
}
cout << k << endl;
}
Centroids——换根DP
Centroids
题意:给你一颗树,让你判断,对于每个点来说,是否可以通过删一条,再添一条边的方法,使它成为一颗树的重心。
重心定义:每个子树的大小不超过
n
2
\dfrac{n}{2}
2n。
思路:
一个点是重心就不用了再改造了。
假设这个点不是重心,如果它存在一个超过
n
2
\dfrac{n}{2}
2n的子树,毫无疑问要操作这个子树,我们找出在这个子树里小于等于
n
2
\dfrac{n}{2}
2n的最大的那个子树
d
p
1
[
m
s
z
1
]
dp1[msz1]
dp1[msz1],然后加到当前根节点上,如果此时使得刚刚那个超过
n
2
\dfrac{n}{2}
2n的子树减掉这个
d
p
1
[
m
s
z
1
]
dp1[msz1]
dp1[msz1]之后不再大于
n
2
\dfrac{n}{2}
2n,就贪心的的认为这个点是重心。
假设这个点不是重心,且它不存在一个超过
n
2
\dfrac{n}{2}
2n的子树,那么超过
n
2
\dfrac{n}{2}
2n的部分一定在
n
−
s
i
z
e
[
u
]
n-size[u]
n−size[u]的一部分,所以我们再维护
d
p
2
[
u
]
dp2[u]
dp2[u]代表着,从u这个点向上走的小于等于
n
2
\dfrac{n}{2}
2n的最大的那个“子树”,此时减掉它加到根上,如果此时使得刚刚那个超过
n
2
\dfrac{n}{2}
2n的“子树”减掉这个
d
p
2
[
u
]
dp2[u]
dp2[u]之后不再大于
n
2
\dfrac{n}{2}
2n,就贪心的的认为这个点是重心。
所以我们要维护
d
p
1
dp1
dp1代表向下的最大的不超过
n
2
\dfrac{n}{2}
2n的子树,维护
d
p
2
dp2
dp2代表向上的最大的不超过
n
2
\dfrac{n}{2}
2n的子树。
细心的你发现了,为什么上边的dp1写的是
d
p
1
[
m
s
z
1
]
而
不
是
d
p
1
[
u
]
呢
dp1[msz1]而不是dp1[u]呢
dp1[msz1]而不是dp1[u]呢?
因为对于向下的这个最大的不超过
n
2
\dfrac{n}{2}
2n的子树,我们不仅要维护最大值
d
p
1
[
m
s
z
1
]
dp1[msz1]
dp1[msz1],还要维护次大
d
p
1
[
m
s
z
2
]
dp1[msz2]
dp1[msz2]。
次大是因为我们在维护dp2的时候,如果
j
j
j点是
u
u
u的最大的子树,那么就类似于《树的中心》那道题维护向上的最远距离一样,此时只能考虑次大值。因为
d
p
2
dp2
dp2维护的就是
j
j
j点向上的最大距离,
u
u
u点的最大距离被
j
j
j占据了之后,你只能使用次大距离来更新
j
j
j点
d
p
2
dp2
dp2的值。如果
u
u
u点的最大距离没有被
j
j
j占据,说明最大距离可能来自其他兄弟结点,不是自己,那此时就可以用最大距离来更新
j
j
j点
d
p
2
dp2
dp2的值。此时
d
p
2
dp2
dp2要么继承父亲上边的最大距离,要么考虑使用兄弟的最大距离,两者取
M
A
X
MAX
MAX。
d
p
1
dp1
dp1很好维护,如果当前子树小于等于
n
2
\dfrac{n}{2}
2n,那就说明找到了,如果当前子树大于
n
2
\dfrac{n}{2}
2n,就递归下去找那个小于等于
n
2
\dfrac{n}{2}
2n的子树。
#include<bits/stdc++.h>
using namespace std;
const int N=3e6+10;
#define int long long
int ne[N],h[N],e[N],idx;
int f[N],siz[N],n,m,ans[N];
int dp1[N],dp2[N],msz1[N],msz2[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs1(int u,int father)
{
siz[u]=1; dp1[u]=dp2[u]=0;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==father) continue;
dfs1(j,u);
siz[u]+=siz[j];
dp1[u]=max(dp1[u],dp1[j]);
if(siz[msz1[u]]<siz[j]) msz2[u]=msz1[u],msz1[u]=j;
else if(siz[msz2[u]]<siz[j]) msz2[u]=j;
}
if(siz[u]<=n/2) dp1[u]=siz[u];
}
void dfs2(int u,int father)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==father) continue;
if(n-siz[j]<=n/2) dp2[j]=n-siz[j];
else
{
if(j==msz1[u])//当自己为最大子树,因为dp2存的是上半部分的最佳子树
//所以不能选取这个子树的节点
{
dp2[j]=max(dp2[u],dp1[msz2[u]]);
// 前者是父亲向上,即之前的上半部分最佳子树
// 后者是兄弟,即最佳子树使用次大值
}
else//自己不是最大子树,那么一定不会选取这个节点的子树,所以可以取msz1
{
dp2[j]=max(dp2[u],dp1[msz1[u]]);
}
}
dfs2(j,u);//先计算再递归
}
if(n-siz[u]>=n/2) ans[u]=(n-siz[u]-dp2[u]<=n/2);
else ans[u]=(siz[msz1[u]]-dp1[msz1[u]]<=n/2);
}
signed main()
{
memset(h, -1, sizeof h);
cin>>n;
for(int i=1;i<=n-1;i++)
{
int a , b;
cin>>a>>b;
add(a,b); add(b,a);
}
dfs1(1,-1);
dfs2(1,-1);
for(int i=1;i<=n;i++)
{
if(i!=n)
cout<<ans[i]<<' ';
else cout<<ans[i];
}
cout<<endl;
}
P2986 [USACO10MAR]Great Cow Gathering G
板子题
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6 + 10;
#define int long long
int ne[N], e[N], w[N], c[N],h[N], high[N], f[N], n, m, idx, siz[N];
int sum;
void init(int u, int father) {
siz[u]=c[u];
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == father) continue;
high[j] = high[u] + w[j];
init(j, u);
siz[u] += siz[j];
}
}
void dfs(int u, int father) {
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == father) continue;
f[j] = f[u] + (long long) w[i] * (sum - (long long)2*siz[j]);
dfs(j, u);
}
}
void add(int a, int b,int c) { e[idx] = b, w[idx]=c,ne[idx] = h[a], h[a] = idx++; }
signed main() {
memset(h, -1, sizeof h);
cin >> n;
for(int i=1;i<=n;i++) {cin>>c[i]; sum+=c[i];}
for (int i = 1; i <= n - 1; i++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
init(1, -1);
for (int i = 1; i <= n; i++) f[1] += (long long )c[i]*high[i];
dfs(1, -1);
int ans = 0x3f3f3f3f3f3f3f, k;
for (int i = 1; i <= n; i++) {
if (ans > f[i]) {
ans = f[i];
}
}
cout << ans << endl;
}
Nearby Cows G——换根DP
终于自己做一发过了qaq
题意:给你一棵 n 个点的树,点带权,对于每个节点求出距离它不超过 k 的所有节点权值和 m。
思路:先预处理出距离不超过 k 的权值和。然后换根,对于当前树的
f
[
j
]
[
k
]
f[j][k]
f[j][k]来说,在换根之前存的是从
j
j
j节点向下,距离不超过 k 的权值和。我们需要把它处理成距离
j
j
j不超过
k
k
k 的权值和。此时显然只需要处理父节点的距离就好了,下边的不需要管。
对于父节点
u
u
u ,距离
u
u
u 结点 深度为
k
k
k 的点 到了
j
j
j 这里变成了深度为
k
+
1
k+1
k+1的点了,所以要扔掉。所以直接加
f
[
u
]
[
k
−
1
]
f[u][k-1]
f[u][k−1]就好了,但因为这个
f
[
u
]
[
k
−
1
]
f[u][k-1]
f[u][k−1]包括了一部分
f
[
j
]
f[j]
f[j]子树的点,要给它减去。
f
[
j
]
[
s
]
=
f
[
j
]
[
s
]
+
f
[
u
]
[
s
−
1
]
−
f
[
j
]
[
s
−
2
]
,
s
∈
[
1
,
k
]
f[j][s] = f[j][s] + f[u][s-1] - f[j][s-2],s∈[1,k]
f[j][s]=f[j][s]+f[u][s−1]−f[j][s−2],s∈[1,k]
不能只写
k
k
k这个状态的转移,二维数组你要尽可能把每个状态都维护出来。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int ne[N],w[N],e[N],h[N],idx;
int n , k;
int f[N][30];
void add(int a,int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs1(int u,int father)
{
for(int i = 0 ; i <= k ;i++) f[u][i] = w[u];
for(int i = h[u] ; ~i ;i = ne[i])
{
int j = e[i];
if(j == father) continue;
dfs1(j,u);
for(int s = 0 ; s <= k ;s ++)
{
f[u][s] += f[j][s-1];
}
}
}
void dfs2(int u,int father)
{
for(int i = h[u]; ~i ; i = ne[i])
{
int j =e[i];
if(j == father) continue;
for(int s = k ; s >= 1;s --)//倒序是因为用到了f[j][s-2]
{ //此处的f[j][s-2]要保证是存的向下不超过k的距离,不能把换完根的状态搞进去
if(s>=2)
f[j][s] = f[j][s] + f[u][s-1] - f[j][s-2];
else f[j][s] = f[j][s] +f[u][s-1];
}
dfs2(j,u);
}
}
int main()
{
memset(h,-1,sizeof h);
cin >> n >> k ;
for(int i = 1; i <= n - 1 ;i ++)
{
int a , b;
cin >> a >> b;
add(a , b);
add(b , a);
}
for(int i = 1 ;i <= n ;i ++) cin>>w[i];
dfs1(1,-1);
dfs2(1,-1);
for(int i = 1 ; i <= n ;i ++)
{
cout<<f[i][k]<<endl;
}
}
Choosing Capital for Treeland
Choosing Capital for Treeland——换根DP
题意:一棵树,有方向,对于一个点 i 它不一定能够到达所有的城市,我们可以通过反转 k 条边的方向,使得它可以到达任意一个城市,对于每个城市 i 求出它最小的 k。( 2<=n<=2e5)。
思路:前向星建图的时候建两遍,一个是原边,一个是扩展边,对于起点做一遍dfs就可以统计出它用了多少拓展边到达所有点。然后再换根,对于子节点和父节点的转移也是看临边是不是扩展边,如果是的话就-1,不是就+1。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6+10;
int ne[N],e[N],h[N],idx;
int vis[N],ans[N],n;//vis标记原边,ans存答案
int res=0x3f3f3f3f3f3f3f;//存最小值
void add1(int a,int b)
{
e[idx]=b,ne[idx]=h[a],vis[idx]=1,h[a]=idx++;
}
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs1(int u,int father)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==father) continue;
dfs1(j,u);
if(!vis[i]) ans[1]++;
}
}
void dfs2(int u,int father)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==father) continue;
if(!vis[i]) ans[j]=ans[u]-1;
else ans[j]=ans[u]+1;
dfs2(j,u);
}
res=min(res,ans[u]);
}
signed main()
{
memset(h, -1, sizeof h);
cin>>n;
for(int i=1;i<=n-1;i++)
{
int a,b;
cin>>a>>b;
add1(a,b);
add(b,a);
}
dfs1(1,-1);//先把1号点的扩展边求出来
res=min(res,ans[1]);
dfs2(1,-1);//换根
cout<<res<<endl;
for(int i=1;i<=n;i++)
{
// cout<<ans[i]<<endl;
if(ans[i]==res) cout<<i<<' ';
}
}
树上染色——树形DP,枚举贡献
我以为这是道换根DP,结果是个树上背包
题意非常简单:
思路的话应该想到树上背包也比较容易:虽然我想不到
我们发现直接求同色的距离不好求,我们考虑先求出根节点的状态再换根,发现也不好求,看来只能是个树形DP。
对于距离的题目,枚举边权两侧的点是个很好的方法。
我们看红边对答案的贡献,显然是红边上边的黑点个数 X 红边下边的黑点个数 X 边的长度。
假设一张图,我们已经把它按照最优的染色方案,把黑色的点撒在图中某些点上,使得边权和最大,我们很容易求出边权——统计子树个数+累加贡献。
现在问题就是我们如何知道它是最优的染色方案,此时我们可以定义:
f
[
i
]
[
j
]
表
示
以
i
为
根
的
子
树
,
染
了
j
个
黑
点
,
使
子
树
的
边
权
按
贡
献
累
加
的
最
大
值
f[i][j]表示以i为根的子树,染了j个黑点,使子树的边权按贡献累加的最大值
f[i][j]表示以i为根的子树,染了j个黑点,使子树的边权按贡献累加的最大值
f
[
u
]
[
s
]
=
m
a
x
(
f
[
u
]
[
s
]
,
f
[
j
]
[
t
]
+
f
[
u
]
[
s
−
t
]
+
n
o
w
)
以
u
为
根
,
j
为
子
节
点
f[u][s] = max(f[u][s],f[j][t] + f[u][s-t] + now)~以 u 为根,j为子节点
f[u][s]=max(f[u][s],f[j][t]+f[u][s−t]+now) 以u为根,j为子节点
s
是
根
要
染
几
个
黑
点
,
t
是
在
当
前
子
树
要
染
几
个
黑
点
,
n
o
w
是
子
树
当
前
染
色
方
案
产
生
的
贡
献
。
s是根要染几个黑点,t是在当前子树要染几个黑点,now是子树当前染色方案产生的贡献。
s是根要染几个黑点,t是在当前子树要染几个黑点,now是子树当前染色方案产生的贡献。
贡
献
:
n
o
w
=
w
[
i
]
∗
(
t
∗
(
k
−
t
)
)
+
w
[
i
]
∗
(
s
i
z
[
j
]
−
t
)
∗
(
n
−
s
i
z
[
j
]
−
(
k
−
t
)
)
;
贡献:now = w[i] * (t * (k - t)) + w[i] * (siz[j] - t) * (n - siz[j] - (k - t));
贡献:now=w[i]∗(t∗(k−t))+w[i]∗(siz[j]−t)∗(n−siz[j]−(k−t));
然后正常做一遍树上背包即可。
不过这道题要初始化为负无穷,且
f
[
u
]
[
0
]
=
f
[
u
]
[
1
]
=
0
f[u][0]=f[u][1]=0
f[u][0]=f[u][1]=0,只有不染黑点和染一个根节点这两种情况没有权值。
负无穷的原因是:比如这个图
红点枚举完绿色子树之后的再枚举蓝色子树,如果要求
f
[
红
色
]
[
3
]
f[红色][3]
f[红色][3],他选了
f
[
蓝
色
]
[
1
]
f[蓝色][1]
f[蓝色][1],此时需要
f
[
绿
色
]
[
2
]
f[绿色][2]
f[绿色][2],但是绿色子树大小不够2,所以会用到不合法的状态,不可以不初始化直接更新。
最后的答案就是
f
[
1
]
[
k
]
f[1][k]
f[1][k]。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 4e4+10;
const int M = 2e3+10;
int ne[N],w[N],e[N],h[N],idx;
int siz[N],ans;
int n , k;
int f[M][M];
void add(int a,int b,int c)
{
e[idx] = b, ne[idx] = h[a],w[idx] = c, h[a] = idx++;
}
void dfs1(int u,int father)
{
siz[u] = 1;
for(int i = h[u]; ~i ; i =ne[i])
{
int j = e[i];
if(j == father) continue;
dfs1(j,u);
siz[u] += siz[j];
}
}
void dfs2(int u,int father)
{
f[u][0] = f[u][1] = 0;
for(int i = h[u]; ~i ; i = ne[i])
{
int j = e[i];
if(j == father) continue;
dfs2(j,u);
for(int s = siz[u] ; s >= 0 ; s--)
{
for(int t = 0 ; t <= siz[j] && t <= s; t++)
{
int now = w[i] * (t * (k - t)) + w[i] * (siz[j] - t) * (n - siz[j] - (k - t));
f[u][s] = max(f[u][s],f[j][t] + f[u][s-t] + now);
}
}
}
}
signed main()
{
memset(h,-1,sizeof h);
memset(f,-0x3f,sizeof f);
cin >> n >> k ;
for(int i = 1; i <= n - 1 ;i ++)
{
int a , b , c;
cin >> a >> b >>c;
add(a , b , c);
add(b , a , c);
}
dfs1(1,-1);
dfs2(1,-1);
cout<<f[1][k]<<endl;
}
优化时间。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2010 * 2;
int h[N] , e[N] , ne[N] , idx , siz[N] , n , m , w[N] ,dp[N][N], k;
void add(int a , int b ,int c)
{
e[idx] = b , ne[idx] = h[a] , w[idx] = c , h[a] = idx ++;
}
void dfs(int u , int father)
{
dp[u][1] = dp[u][0] = 0;
siz[u] = 1;
for(int i = h[u] ; ~ i ; i = ne[i])
{
int j = e[i];
if(j == father) continue;
dfs(j , u);
for(int s = siz[u]; s >= 0 ; s --)
{
for(int t = siz[j] ;t >= 0 ; t --)
{
int now = (k - t) * t * w[i] + (n - k - (siz[j] - t)) * (siz[j] - t) * w[i];
dp[u][s + t] = max(dp[u][s + t], now + dp[u][s] + dp[j][t]);
}
}
siz[u] += siz[j];
}
}
signed main()
{
memset(h , -1 , sizeof h);
memset(dp , -0x3f , sizeof dp);
cin >> n >> k;
for(int i = 1 ; i <= n - 1; i ++)
{
int a , b , c;
cin >> a >> b >> c;
add(a , b , c);
add(b , a , c);
}
dfs(1 , -1);
cout << dp[1][k] << endl;
}
选数——二维费用背包
选数
题意:
给定
n
n
n 个整数
a
1
,
a
2
,
…
,
a
n
。
a1,a2,…,an。
a1,a2,…,an。
请你从中选取恰好 k k k 个数,要求选出的数的乘积的末尾 0 0 0 的数量尽可能多。
请输出末尾
0
0
0 的最大可能数量。
1
≤
n
≤
200
,
1
≤
k
≤
n
,
1
≤
a
i
≤
1
0
18
。
1≤n≤200 ,1≤k≤n,1≤ai≤10^{18}。
1≤n≤200,1≤k≤n,1≤ai≤1018。
思路:
f
[
i
]
[
k
]
[
j
]
表
示
前
i
个
数
选
k
个
数
总
共
含
有
j
个
因
子
5
,
取
得
因
子
2
个
数
最
大
值
f[i][k][j]表示前i个数选k个数总共含有j个因子5,取得因子2个数最大值
f[i][k][j]表示前i个数选k个数总共含有j个因子5,取得因子2个数最大值
对于一个序列,选出的每一个
10
10
10,都需要一个
2
2
2和
5
5
5。
看数据范围不可能把选完的结果算出来再统计,因为空间存不下。
所以我们看选出来的数对答案的贡献,每一个2匹配一个5可以产生一个10,所以一个序列选出
k
k
k个数,最后的答案就是
m
i
n
(
s
u
m
5
,
s
u
m
2
)
min(sum_5,sum_2)
min(sum5,sum2)。
在选出来的数中,我们希望
5
5
5 和
2
2
2 尽可能的多,所以我们把
5
5
5 当做体积,
2
2
2当做权值,在每一步的更新中,都是取相同的
5
5
5提供更多的
2
2
2.
把5作为体积的好处是
a
[
i
]
=
1
e
18
,
l
o
g
5
1
e
18
≈
25
,
而
l
o
g
2
1
e
18
≈
64
,
显
然
5
作
为
体
积
来
说
开
的
数
组
空
间
比
较
小
a[i]=1e18,log_5{1e18}≈25,而log_2{1e18}≈64,显然5作为体积来说开的数组空间比较小
a[i]=1e18,log51e18≈25,而log21e18≈64,显然5作为体积来说开的数组空间比较小。所以此时体积只需要开
25
∗
200
就
够
用
了
25*200就够用了
25∗200就够用了。
再就是这个题是从
n
n
n 个数选
k
k
k 个,所以这是个二维费用背包,还有一维体积,就是每个数本身还占了 体积为 1 的空间,这个很好理解。
所以总的复杂度是
20
0
前
i
个
数
∗
20
0
选
k
个
数
∗
500
0
5
的
个
数
200_{前i个数}*200_{选k个数}*5000_{5的个数}
200前i个数∗200选k个数∗50005的个数,大概是
2
e
9
2e9
2e9,当然我们知道每个数其实用5为底数到达
a
i
a_i
ai的最大值其实是25,所以这个地方枚举的体积从
i
∗
25
i*25
i∗25枚举也是一样的,总体来说就是
1
e
8
1e8
1e8的复杂度。
空间的话第一维直接滚动数组滚掉,开一个dp
[
200
]
[
5000
]
[200][5000]
[200][5000]就够用。
还有就是这个题第一维的体积是恰好选 k 个,第二维的因数 5 是 恰好含有 j 个。
所以我们要避开非法的范围,初始化为
d
p
[
0
]
[
0
]
=
0
dp[0][0]=0
dp[0][0]=0,其他全是负无穷代表非法。
比如:
d
p
[
0
]
[
5
]
=
−
0
x
3
f
3
f
3
f
3
f
dp[0][5]=-0x3f3f3f3f
dp[0][5]=−0x3f3f3f3f的含义就是,你取0个数,得到5个5是不合法的——即你取0个数不可能恰好得到5个5作为因子。
后边的就是正常的二维背包板子了,最后记得扫一遍
d
p
dp
dp数组取
m
i
n
min
min。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 210, M = 5010;
int n, m;
int v[N], w[N];
int f[N][M];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
LL x;
cin >> x;
while (x % 5 == 0) x /= 5, v[i] ++ ;
while (x % 2 == 0) x /= 2, w[i] ++ ;
}
memset(f, -0x3f, sizeof f);
f[0][0] = 0;
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 1; j -- )
for (int k = i * 25; k >= v[i]; k -- )
f[j][k] = max(f[j][k], f[j - 1][k - v[i]] + w[i]);
int res = 0;
for (int i = 1; i < M; i ++ )
res = max(res, min(i, f[m][i]));
cout << res << endl;
return 0;
}
垃圾陷阱——有限制的选择问题(背包)
题意:奶牛在深度为D井底,随着时间丢下 G 个垃圾,可以选择吃一些垃圾维持体力,也可以选择垫在脚下一些帮助自己离开井。求最早离开井的时间。
#include<bits/stdc++.h>
#define int long long
int f[210][25100];
int D,G;
struct node
{
int t,l,h;
}a[210];
using namespace std;
bool cmp(node a,node b )
{
return a.t<b.t;
}
signed main()
{
cin>>D>>G;
for(int i = 1; i <= G ; i ++)
{
scanf("%lld %lld %lld",&a[i].t,&a[i].l,&a[i].h);
}
sort(a+1,a+1+G,cmp);
memset(f,-0x3f,sizeof f);
f[0][0] = 10;
for(int i = 1 ; i <= G ;i ++)
{
for(int j = 0; j <= D; j ++)
{
if(f[i-1][j] < a[i].t - a[i-1].t) continue;
if(j + a[i].h >= D)
{
cout<<a[i].t<<endl;
return 0;
}
f[i][j+a[i].h] = max(f[i][j+a[i].h],f[i-1][j] - (a[i].t - a[i - 1].t));
f[i][j] = max(f[i][j], f[i - 1][j] + a[i].l - (a[i].t - a[i - 1].t));
}
}
int ans = 10;
for(int i=1;i<=G;i++)
{
if(ans<a[i].t) break;
ans+=a[i].l;
}
cout<<ans<<endl;
return 0;
}
有线电视网(背包板子)
#include<bits/stdc++.h>
using namespace std;
const int N = 2 * 3010;
#define int long long
int h[N] , e[N] , ne[N] , idx , a[N] , n , m , w[N] ,siz[N];
int dp[3010][3010];
void add(int a , int b ,int c)
{
e[idx] = b , ne[idx] = h[a] , w[idx] = c , h[a] = idx ++;
}
void dfs(int u ,int father)
{
dp[u][0] = 0;
dp[u][1] = a[u];
if(a[u]) siz[u] = 1;
for(int i = h[u] ; ~ i ; i = ne[i])
{
int j = e[i];
if(j == father) continue;
dfs(j , u);
for(int s = siz[u]; s >= 0 ; s--)
{
for(int t = siz[j] ;t >= 0; t--)
{
dp[u][s + t] = max(dp[u][s + t],dp[j][t] + dp[u][s] - w[i]);
}
}
siz[u]+=siz[j];
}
}
signed main()
{
memset(h , -1 , sizeof h);
memset(dp,-0x3f,sizeof dp);
memset(a , -0x3f , sizeof a);
cin >> n >> m;
for(int i = 1 ; i <= n - m; i ++)
{
int k;
scanf("%lld" , &k);
for(int j = 1 ; j <= k ; j ++)
{
int p , q;
scanf("%lld %lld", &p , &q);
add(i , p , q);
add(p , i , q);
}
}
for(int i = n - m + 1 ; i <= n ; i ++) scanf("%lld",&a[i]);
dfs(1 , -1);
int ans = -1;
for(int i = 1; i <= n ; i ++)
{
if(dp[1][i] >= 0)
{
ans = max(i , ans);
}
}
cout << ans << endl;
}
有依赖的背包
#include<bits/stdc++.h>
using namespace std;
const int N = 2 * 301;
#define int long long
int h[N] , e[N] , ne[N] , idx , a[N] , n , m , w[N] ,siz[N] , d[N];
int dp[601][601];
void add(int a , int b )
{
e[idx] = b , ne[idx] = h[a] , h[a] = idx ++;
}
void dfs(int u)
{
siz[u] = 1;
for(int i = 1; i <= m ; i ++) dp[u][i] = w[u];
for(int i = h[u] ; ~ i ; i = ne[i])
{
int j = e[i];
dfs(j);
for(int s = siz[u]; s >= 1 ; s --)
{
for(int t = siz[j] ;t >= 0; t --)
{
dp[u][s + t] = max(dp[u][s + t],dp[j][t] + dp[u][s]);
}
}
siz[u]+=siz[j];
}
}
signed main()
{
memset(h , -1 , sizeof h);
memset(dp , -0x3f ,sizeof dp);
cin >> n >> m;
m ++;
for(int i = 1 ; i <= n; i ++)
{
int a , b ;
cin >> a >> b;
if(a != 0)
{
add(a , i);
d[i] ++;
}
w[i] = b;
}
int ans = -0x3f3f3f3f;
for(int i = 1; i <= n ; i ++)
{
if(!d[i])
{
add(0 , i);
}
}
dfs(0);
cout << dp[0][m] << endl;
}