P2014 选课 (依赖性背包,树上背包) U53204 【数据加强版】选课(后序遍历优化)
这道背包和普通的背包不一样,这题的物品是有依赖,即要选这个物品必须要先选它的前置物品才行,这就变成了依赖性背包,很明显这样会就产生森林,森林的的话不好处理,加个价值为0的虚拟节点0就可以组成一棵树,那就好处理多了.
dp[i][j][k]表示在i的子树里从前j棵子树里取若干个物品放入容量k的包里能得到的最大价值,一般的树形dp里,父节点的dp值都是儿子转移来的。
可以把每个子树视为一个泛化物品,一开始搜到u时,u子树的泛化物品只包括u物品,然后通过把vi子树的泛化物品求出来,然后依次和u节点的泛化物品合并(这个合并就是用的2个泛化物品合并的方法,先枚举k,包容量,然后枚举v,分配给包u的容量)就可以得到u子树的泛化物品。然后因为要选物品必须要先选它的前置物品才行,所以我们
对每个初始化 for(int k=1;k<=m+1;k++) dp[u][0][k]=val[u],这样子才能保证最后得到的u子树的泛化物品的dp[u][j](j>=1)选取了u物品。
原版:n*m^2 这篇论文有nm的泛化物品优化方法,但是我看不懂
#include<bits/stdc++.h>
using namespace std;
#define ls rt<<1
#define rs (rt<<1)+1
#define ll long long
#define fuck(x) cout<<#x<<" "<<x<<endl;
const int maxn=1e6+10;
int d[4][2]={1,0,-1,0,0,1,0,-1};
int dp[305][305][305],val[305],n,m;
bool vis[305];
vector<int>g[305];
void dfs(int u){
for(int k=1;k<=m+1;k++) dp[u][0][k]=val[u];
for(int i=0;i<g[u].size();i++){
dfs(g[u][i]);
for(int k=1;k<=m+1;k++){
dp[u][i+1][k]=dp[u][i][k];
for(int v=1;v<=k;v++)
dp[u][i+1][k]=max(dp[u][i+1][k],dp[u][i][v]+dp[g[u][i]][(int)(g[g[u][i]].size())][k-v]);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int tmp;
scanf("%d%d",&tmp,&(val[i]));
if(tmp!=0)
vis[i]=1,g[tmp].push_back(i);
}
for(int i=1;i<=n;i++)
if(!vis[i])
g[0].push_back(i);
dfs(0);
cout<<dp[0][(int)g[0].size()][m+1]<<endl;
return 0;
}
v#include<bits/stdc++.h>
using namespace std;
#define ls rt<<1
#define rs (rt<<1)+1
#define ll long long
#define fuck(x) cout<<#x<<" "<<x<<endl;
const int maxn=1e6+10;
int d[4][2]={1,0,-1,0,0,1,0,-1};
int dp[305][305],val[305],n,m;
bool vis[305];
vector<int>g[305];
void dfs(int u){
for(int k=1;k<=m+1;k++) dp[u][k]=val[u];
for(int i=0;i<g[u].size();i++){
dfs(g[u][i]);
for(int k=m+1;k>=1;k--){
for(int v=1;v<=k;v++)
dp[u][k]=max(dp[u][k],dp[u][v]+dp[g[u][i]][k-v]);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int tmp;
scanf("%d%d",&tmp,&(val[i]));
if(tmp!=0)
vis[i]=1,g[tmp].push_back(i);
}
for(int i=1;i<=n;i++)
if(!vis[i])
g[0].push_back(i);
dfs(0);
cout<<dp[0][m+1]<<endl;
return 0;
}
加强版:
这题数据范围很大,用之前的方法过不了,用了后序遍历来优化,求出树的后序遍历之和,求这个遍历序列对应的物品做一次类似01背包的dp就好了,dp[i][j]表示从前i个物品选若干物品放容量j的包的最大价值,转移方程是dp[i][j]=max(dp[i-1][j-1]+wi,dp[i-sz[i]][j])
#include<bits/stdc++.h>
using namespace std;
#define ls rt<<1
#define rs (rt<<1)+1
#define ll long long
#define fuck(x) cout<<#x<<" "<<x<<endl;
const int maxn=1e5+10;
int d[4][2]= {1,0,-1,0,0,1,0,-1};
inline ll read() {
ll s = 0,w = 1;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') w = -1;
ch = getchar();
}
while(isdigit(ch))
s = s * 10 + ch - '0',ch = getchar();
return s * w;
}
inline void write(ll x) {
if(x < 0)
putchar('-'), x = -x;
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
int n,m,val[maxn],afo[maxn],sz[maxn],cnt;
vector<int>g[maxn],dp[maxn];
void dfs(int now){
sz[now]=1;
for(int i=0;i<g[now].size();i++)
dfs(g[now][i]),sz[now]+=sz[g[now][i]];
afo[++cnt]=now;
}
int main()
{
n=read(),m=read();
for(int i=0; i<=n+1; i++) {
dp[i].reserve(m+3);
for (int j = 1; j <= m + 2; j++)
dp[i].push_back(0);
}
for(int i=1; i<=n; i++)
{
int tmp;
tmp=read(),val[i]=read();
g[tmp].push_back(i);
}
dfs(0);
for(int i=1;i<=cnt;i++){
for(int j=1;j<=m+1;j++){
dp[i][j]=max(dp[i-sz[afo[i]]][j],dp[i-1][j-1]+val[afo[i]]);
}
}
write(dp[cnt][m+1]);
puts("");
return 0;
}