DP小题单【4】

DP小题单【1】
DP小题单【2】
DP小题单【3】

几道板子题——换根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] nsize[j]
所以两个结果状态的差值就是 n − 2 ∗ s i z e [ j ] n - 2*size[j] n2size[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] nsize[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

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][k1]就好了,但因为这个 f [ u ] [ k − 1 ] f[u][k-1] f[u][k1]包括了一部分 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][s1]f[j][s2],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<<' ';
    }
}

P3177 [HAOI2015]树上染色

树上染色——树形DP,枚举贡献

我以为这是道换根DP,结果是个树上背包
题意非常简单:
在这里插入图片描述
思路的话应该想到树上背包也比较容易:虽然我想不到
在这里插入图片描述
我们发现直接求同色的距离不好求,我们考虑先求出根节点的状态再换根,发现也不好求,看来只能是个树形DP。
对于距离的题目,枚举边权两侧的点是个很好的方法。
我们看红边对答案的贡献,显然是红边上边的黑点个数 X 红边下边的黑点个数 X 边的长度。
假设一张图,我们已经把它按照最优的染色方案,把黑色的点撒在图中某些点上,使得边权和最大,我们很容易求出边权——统计子树个数+累加贡献。
现在问题就是我们如何知道它是最优的染色方案,此时我们可以定义:
f [ i ] [ j ] 表 示 以 i 为 根 的 子 树 , 染 了 j 个 黑 点 , 使 子 树 的 边 权 按 贡 献 累 加 的 最 大 值 f[i][j]表示以i为根的子树,染了j个黑点,使子树的边权按贡献累加的最大值 f[i][j]ij使
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][st]+now) uj s 是 根 要 染 几 个 黑 点 , t 是 在 当 前 子 树 要 染 几 个 黑 点 , n o w 是 子 树 当 前 染 色 方 案 产 生 的 贡 献 。 s是根要染几个黑点,t是在当前子树要染几个黑点,now是子树当前染色方案产生的贡献。 st,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(kt))+w[i](siz[j]t)(nsiz[j](kt));
然后正常做一遍树上背包即可。
不过这道题要初始化为负无穷,且 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}。 1n2001kn1ai1018
思路:
f [ i ] [ k ] [ j ] 表 示 前 i 个 数 选 k 个 数 总 共 含 有 j 个 因 子 5 , 取 得 因 子 2 个 数 最 大 值 f[i][k][j]表示前i个数选k个数总共含有j个因子5,取得因子2个数最大值 f[i][k][j]ikj52
对于一个序列,选出的每一个 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,log51e1825log21e18645。所以此时体积只需要开 25 ∗ 200 就 够 用 了 25*200就够用了 25200
再就是这个题是从 n n n 个数选 k k k 个,所以这是个二维费用背包,还有一维体积,就是每个数本身还占了 体积为 1 的空间,这个很好理解。
所以总的复杂度是 20 0 前 i 个 数 ∗ 20 0 选 k 个 数 ∗ 500 0 5 的 个 数 200_{前i个数}*200_{选k个数}*5000_{5的个数} 200i200k50005,大概是 2 e 9 2e9 2e9,当然我们知道每个数其实用5为底数到达 a i a_i ai的最大值其实是25,所以这个地方枚举的体积从 i ∗ 25 i*25 i25枚举也是一样的,总体来说就是 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;
    
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值