真的,,,好久都没有写过动态规划了呀....
题意说的是对于n件物品,每一件物品都存在一个价格c和一张优惠券能够减免d元。对于第i件物品(i ≥ 2)使用优惠券时有额外的要求,那就是第xi张优惠券必须被使用,满足1<=xi
一个比较显然的事情就是这些物品使用优惠券时构成了一棵依赖树,以1号物品为root节点,这是由1<= xi
接下来这个问题需要被转化一下,不能直接求出给出d元金钱下的最多物品数,转化为求对于确定的物品数量k,所消耗的最少的金钱数。最后求答案的时候求一个最大的k就好
首先明确一点,对于每个物品都存在两个状态,使用或者不使用优惠券。
定义状态f[u][i]表示以u为根且u使用优惠券的子树购买i件物品时使用最小花费
定义状态g[u][i]表示以u为根且u及其所有下属节点均不使用优惠券时购买i件物品的最小花费
我自己是第一次写这样的树型DP....要维护一个当前树的size(一下简写为sz),意思就是一开始当前树仅仅只有自己一个根节点,然后不断把子树合并上来来扩充sz
首先,对于任何一个节点,按照定义,初始状态都有:
f[u][1]=c[i]-d[i],g[u][1]=c[i]
f[u][0]=g[u][0]=0
sz[u]=1
每一次考虑u的一个子节点v,用一个二重循环枚举在当前u上拿i件物品,在其新子树(此处的新仅仅指没有还没有加入到u中)v上拿j件物品,来更新f[u][i+j],g[u][i+j],然后把v合并到u中。
状态转移方程写出来就是
f[u][i+j]=min(f[u][i+j],f[u][i]+min(f[v][j],g[v][j]))
g[u][i+j]=min(g[u][i+j],g[u][i]+g[v][j])
注意的一点就是在计算f[u][i+j]的时候,若i==0,不满足我们的定义,因此此时的代价应该是0x3f3f3f3f
此外一个小trick就是在每次做完当前树u之后,对于所有的1<= i <= sz[u],令f[u][i]=min(f[u][i],g[u][i])
即可在后续计算中把状态转移方程f[u][i+j]=min(f[u][i+j],f[u][i]+f[v][j])
结尾的附录写出了关于时间复杂度的分析
#include
#include
#include
using namespace std;
const int maxm=5010;
struct Edge{
int nt,v;
explicit Edge(int a=0,int b=0):nt(a),v(b){};
}edge[maxm];
int n,b,c[maxm],d[maxm],sz[maxm],fi[maxm],rec,f[maxm][maxm],g[maxm][maxm];
inline void addeage(int x,int y);
void dfs(int now);
int main(){
ios_base::sync_with_stdio(0);
cin>>n>>b>>c[1]>>d[1];
for(int i=2,tmp;i<=n;++i)
cin>>c[i]>>d[i]>>tmp,addeage(tmp,i);
dfs(1);
for(int i=n;i>=0;--i)
if(f[1][i]<=b)
cout<=0;--i)
for(int j=sz[v];j;--j)
g[now][i+j]=min(g[now][i+j],g[now][i]+g[v][j]),
f[now][i+j]=min(f[now][i+j],i?f[now][i]+f[v][j]:0x3f3f3f3f);
sz[now]+=sz[v];
}
for(int i=1;i<=sz[now];++i)
f[now][i]=min(f[now][i],g[now][i]);
}
appendix:有关于时间复杂度的分析:
时间复杂度实际上是O(n²)的
咋一看对于一个节点应该是n²的计算量,由于对n个节点都计算一次,最后应该是n³的复杂度。
让我们来详细分析一下
对于一个节点的计算的分析:(为了方便起见,以下的sz[1],sz[2].....sz[k]表示当前被计算节点的第XX个子树)
计算量是:sz[1]*1 + sz[2]*(sz[1]+1) + sz[3]*(sz[2]+sz[1]+1)....... --> ∑s[i]*s[j] +∑s[i] (1<= j < i ) -->( (1 + ∑sz[i])² - ∑sz[i]² - 1)/2 (注意平方的位置)
没错,对于一个节点来说计算量分析出来确实是n²的
把式子改写一下,忽略常数项有
(sz[当前树])² - ∑sz[某颗子树]²
当我们把所有节点的计算加到一起的时候,发现里面项是可以相互抵消的,因此总时间复杂度还是n²
Q.E.D