【解题报告】CF815C
题目大意
Kellen想要买 (n) 件东西,她有 (b) 块钱,实际上,超市也就只有 (n) 件东西,超市为Kellen准备了 (n) 种优惠券,但是对于除第一件商品以外的优惠券,必须在使用第 (x_i) 张优惠券之后方才可以使用,你能帮她了解一下在不超过她手上的钱的情况下,她最多能买多少东西
输入第一行两个整数,(n) 和 (b)
从第二行到第 (n+1) 行,除第二行前两个整数之外,后面的每行三个整数 $c_i space,d_i space $ 和 (x_i)
字母意义皆如题目所示
思路
这道题目,我们可以从必须使用 (x_i) 之后才可以使用 (i) 知道,这个类似于没有上司的舞会,或者是选课,因此我们要使用树形DP
按照之前的惯例,我们可以设置如下状态
设置 (f[i][j][0/1]) 表示在以 (i) 为根的子树中,选择 (j) 个物品,在第 $i $ 个商品上面是否使用优惠券可以最多购买东西的数量
然后我们根据定义,我们可以列出如下初始状态
- 第 (x) 个节点的子树不购买,他自己也不购买,不使用优惠券,没有代价
- 第 (x) 个节点的子树不购买,购买他自己,但是也不使用优惠券,代价是买 (x) 花的钱 (c_x)
- 第 (x) 个节点地字数不购买,购买他自己,使用优惠券,这样他们的代价就是 (c_x-d_i)
所以我们的初始状态就定义完了
然后想动态转移方程
怎么想呢,枚举自己的大小和子树和他所有儿子的大小
我们可以有
[f[x][i+j][0]=min(f[x][i+j][0],f[x][i][0]+f[son][j][0])
]
这个的意思呢就是x节点有 (i+j) 个节点,然后选除了某个子树外的 (i) 个节点的买的数量和买这某个子树的 (j) 个节点的总的可以买的
[f[x][i+j][1]= min egin{cases} f[x][i][1]+f[son][j][0] \ f[x][i][1]+f[son][j][1] end{cases}
]
这个方程的意思和上面的差不多,也就不做过多解释了
因此我们对着是输入的数据疯狂建树,然后将自己的边指向需要自己的节点,然后我们跑一边 dfs 就可以把树形DP写完了
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int maxn=5005;
int n,b,ans;
int c[maxn],d[maxn];
int x[maxn];
int size[maxn],f[maxn][maxn][5];
struct edge{
int e,next;
}ed[maxn<<1];
int en,first[maxn];
void add_edge(int s,int e)
{
en++;
ed[en].next=first[s];
first[s]=en;
ed[en].e=e;
}
void dfs(int x)
{
size[x]=1;
f[x][0][0]=0;
f[x][1][0]=c[x];
f[x][1][1]=c[x]-d[x];
for(int i=first[x];i;i=ed[i].next)
{
int e=ed[i].e;
dfs(e);
for(int i=size[x];i>=0;i--)
{
for(int j=0;j<=size[e];j++)
{
f[x][i+j][0]=min(f[x][i+j][0],f[x][i][0]+f[e][j][0]);
f[x][i+j][1]=min(f[x][i+j][1],min(f[x][i][1]+f[e][j][0],f[x][i][1]+f[e][j][1]));
}
}
size[x]+=size[e];
}
}
int main()
{
memset(f,0x3f3f3f,sizeof(f));
cin>>n>>b;
cin>>c[1]>>d[1];
for(int i=2;i<=n;i++)
{
cin>>c[i]>>d[i]>>x[i];
add_edge(x[i],i);
}
dfs(1);
for(int i=n;i>=1;i--)
{
if(f[1][i][0]<=b||f[1][i][1]<=b)
{
ans=i;
break;
}
}
cout<<ans<<'
';
return 0;
}