洛谷P2014 选课 | P2515 [HAOI2010]软件安装 | P1273 有线电视网【树上背包】

本文介绍了一种动态规划算法,用于解决课程选择最大化问题和有线电视网用户数量最大化的类似问题。通过定义dp[u][k]表示以u为根的子树内选择不超过k门课程的最大价值,文章详细阐述了状态转移方程,并针对不同问题进行了适当的调整。

P2014 选课

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?

输入格式:

第一行有两个整数N,M用空格隔开。(1<=N<=300,1<=M<=300)

接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。

输出格式:

只有一行,选M门课程的最大得分。


题目分析

d p [ u ] [ k ] dp[u][k] dp[u][k]表示以u为根的子树内选择不超过k门课能获得的最大价值
d p [ u ] [ k ] = m a x ( d p [ v i ] [ i ] + d p [ v j ] [ k − i ] ) dp[u][k]=max(dp[v_i][i]+dp[v_j][k-i]) dp[u][k]=max(dp[vi][i]+dp[vj][ki])
由于dfs每次从一个子树回来都要更新 d p [ u ] dp[u] dp[u]
所以 d p [ u ] dp[u] dp[u]已经保存了前半部分子树转移来的dp值,并不需要 O ( n 2 ) O(n^2) O(n2)的枚举,不过注意要倒序
而由于课程关系可能组成森林,所以没有先修要求的课都连向0号点


#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=310;
int n,m;
int a[maxn];
struct node{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int judge[maxn];
int dp[maxn][maxn];

void add(int u,int v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot;
}

void DP(int u)
{
    for(int i=1;i<=m;++i)//要在u的子树内选择,要先选u
    dp[u][i]=a[u];
    for(int i=head[u];i;i=E[i].nxt) 
	{
        int v=E[i].v;
        DP(v); 
		
        for(int j=m-1;j>=0;--j) //注意倒叙
        for(int k=0;k<=j;k++)
        dp[u][j+1]=max(dp[u][j+1],dp[v][k]+dp[u][j+1-k]);
    }
}

int main()
{
    n=read(); m=read()+1;//由于0号节点也算在内,所以+1
    for(int i=1;i<=n;++i)
    {
        int fa=read(); a[i]=read();
        add(fa,i);
    }
    DP(0);
    printf("%d",dp[0][m]);
    return 0;
} 


P2515 [HAOI2010]软件安装算是上面这题的加强版
选择的条件关系可能成,以及选课数改成了给定的体积

对于环,一个环内要么必须全选要么全部不能选,所以只要Tarjan缩点重新建树即可
对于给定体积只需要对dp方程稍作修改

void DP(int u) 
{
    for(int i=weight[u];i<=m;++i)//把所有1改成weight即可
    dp[u][i]=val[u];
    for(int i=head[u];i;i=E[i].nxt) 
	{
        int v=E[i].v;
        DP(v); 
		
        for(int j=m-weight[u];j>=0;--j) 
        for(int k=0;k<=j;k++)
        dp[u][j+weight[u]]=max(dp[u][j+weight[u]],dp[v][k]+dp[u][j+weight[u]-k]);
    }
}

P1273 有线电视网

时空限制 1000ms / 128MB

题目描述

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。

从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。

现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。

写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。

输入格式:

输入文件的第一行包含两个用空格隔开的整数N和M,其中2≤N≤3000,1≤M≤N-1,N为整个有线电视网的结点总数,M为用户终端的数量。

第一个转播站即树的根结点编号为1,其他的转播站编号为2到N-M,用户终端编号为N-M+1到N。

接下来的N-M行每行表示—个转播站的数据,第i+1行表示第i个转播站的数据,其格式如下:

K A1 C1 A2 C2 … Ak Ck

K表示该转播站下接K个结点(转播站或用户),每个结点对应一对整数A与C,A表示结点编号,C表示从当前转播站传输信号到结点A的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。

输出格式:

输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。


题目分析

和上面几乎一样的套路
d p [ u ] [ k ] dp[u][k] dp[u][k]表示以u为根的子树选择k个用户能获得的最大价值

初始全为-INF
状态转移方程除了要处理边权外和上面一样
但需要注意,总底部向上传的size只需要记录叶子节点的数量

最后从m~1遍历,若 d p [ 1 ] [ i ] > = 0 dp[1][i]>=0 dp[1][i]>=0,直接输出 i i i即可


#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=3010;
int n,m;
struct node{int v,dis,nxt;}E[maxn<<1];
int head[maxn],tot;
int a[maxn],dp[maxn][maxn];

void add(int u,int v,int dis)
{
    E[++tot].nxt=head[u];
    E[tot].v=v; E[tot].dis=dis;
    head[u]=tot;
}

int dfs(int u)
{
    if(u>n-m){ dp[u][1]=a[u]; return 1;}
    int size=0; dp[u][0]=0;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v,sz=dfs(v); 
		size+=sz;
        for(int j=size;j>0;--j)
        for(int k=j;k>=0;--k)
        dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-E[i].dis);
    }
    return size;
}

int main()
{
    n=read();m=read();
    for(int u=1;u<=n-m;++u)
    {
    	int k=read();
    	while(k--){
    		int v=read(),dis=read();
    		add(u,v,dis); //add(v,u,dis);
        }
    }
    
    for(int i=n-m+1;i<=n;++i) a[i]=read();
    memset(dp,128,sizeof(dp)); dfs(1);
    
    for(int i=m;i>=0;--i)
    if(dp[1][i]>=0){ printf("%d",i); break;}
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值