【解题报告】CF815C
【解题报告】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 \begin{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<<'\n';
return 0;
}
本博文为wweiyi原创,若想转载请联系作者,qq:2844938982