题意
马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街。商店街有n个商店,并且它们之间的道路构成了一颗树的形状。
第i个商店只卖第i种物品,小苗对于这种物品的喜爱度是wi,物品的价格为ci,物品的库存是di。但是商店街有一项奇怪的规定:如果在商店u,v买了东西,并且有一个商店w在u到v的路径上,那么必须要在商店w买东西。小葱身上有m元钱,他想要尽量让小苗开心,所以他希望最大化小苗买到物品的喜爱度之和。这种小问题对于小葱来说当然不在话下,但是他的身边没有电脑,于是他打电话给同为OI选手的你,你能帮帮他吗?
N<=500,M<=4000,T<=5,Wi<=4000,Di<=100
分析
显然我们买东西的节点必然构成一棵连通子树,考虑最暴力的树形dp,我们有两种做法:
一种是设f[i,j]表示以节点i为根的子树且必选i用了j元的最大价值,然后每次把儿子所有合并上来,复杂度是O(nm^2)
一种是枚举每个节点作为根,然后作树形依赖背包,复杂度是O(n^2m)。
发现这两种算法都不能通过本题。
注意到第一种算法每次合并两个背包的复杂度是O(m^2)的,但这显然很不优秀因为一棵子树的节点数量是O(n)且在背包中加入一个点的复杂度是O(m)。也就是说我们每次合并背包还不如把整颗子树的节点加入到某个背包中。
这时想到了什么?dsu on tree!
加入节点的时候用单调队列优化一下,那么时间复杂度就是O(nmlogn)。
这题还可以用点分治做,就是每次从分治中心为根开始做依赖背包,时间复杂度也是O(nmlogn)。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=505;
const int M=4005;
const int inf=1000000000;
int n,m,c[N],d[N],w[N],f[N][M],last[N],cnt,son[N],size[N],q1[M],q2[M],g[N][M];
struct edge{
int to,next;}e[N*2];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'|