题解:P6326 Shopping

题解区单调队列优化的题解好像不多(?),怎么都是二进制拆分打爆单调队列的。来水一发题解。

首先转化题意,题目即为求一个连通块使其价格之和不超过 \(m\) 且喜爱度之和最大。如果我们固定一个点为根,要求这个连通块必须包含这个点,那么就是很朴素的树上多重背包。

具体的,先跑一边 dfs 序,然后对于 \(f_{i,j}\) 表示后以 dfs 序第 \(i\) 到第 \(n\) 个点、共花费 \(j\) 元能获得的最大喜爱度,设其 dfs 序为 \(i\) 的点是 \(u\)。如果 \(u\) 不拿,那么其子树中的都不能取即 \(f_{i,j}=f_{i+siz_u,j}\),或者取了 \(u\) 那么 \(f_{i,j}=\max_{1 \leq k \leq d_u} f_{i+1,j-k\times c_i}+k\times w_i\),直接使用单调队列维护即可。

如果对每个点为根跑一次树上背包显然时间复杂度会爆。而我们发现这个是可以点分治优化的(其实就是有许多相同的连通块被算了很多次),直接做就完了,时间复杂度为 \(\mathcal{O}(nm \log n)\)。注意转移的时候 \(u\) 应至少取一次。

const int N=505,M=4005,inf=1e9;
int T,n,m,w[N],c[N],d[N],ans;
vector<int> edge[N];

int rt,sum,siz[N],mxsiz[N];
int tim,dfn[N],lim[N],f[N][M];
bitset<N> vis;
inline void dfs(int u,int fa) {
	siz[u]=1,lim[dfn[u]=++tim]=u;
	for(int v:edge[u]) {
		if(v==fa||vis[v]) continue ;
		dfs(v,u),siz[u]+=siz[v];
	}
	return ;
}
inline void get_root(int u,int fa) {
	mxsiz[u]=0;
	for(int v:edge[u]) {
		if(v==fa||vis[v]) continue ;
		get_root(v,u),mxsiz[u]=max(mxsiz[u],siz[v]);
	}
	mxsiz[u]=max(mxsiz[u],sum-siz[u]);
	if(mxsiz[u]<mxsiz[rt]) rt=u;
	return ;
}
inline int calc(int i,int h,int j,int v) {
	return f[i+1][j*c[v]+h]-w[v]*j;
}
inline void solve(int u) {
	vis[u]=1,tim=0,dfs(u,0);
	for(int i=1;i<=tim+1;i++) for(int j=0;j<=m;j++) f[i][j]=0;
	for(int i=tim;i>=1;i--) {
		for(int j=0;j<=m;j++) f[i][j]=f[i+siz[lim[i]]][j]; // i 子树都不取
		int v=lim[i];
		for(int h=0;h<c[v];h++) {
			deque<int> dq; // 单调队列优化多重背包
			for(int j=0;j*c[v]+h<=m;j++) {
				while(!dq.empty()&&dq.front()<j-d[v]) dq.pop_front();
				if(!dq.empty()) f[i][j*c[v]+h]=max(f[i][j*c[v]+h],calc(i,h,dq.front(),v)+w[v]*j);
				while(!dq.empty()&&calc(i,h,dq.back(),v)<=calc(i,h,j,v)) dq.pop_back();
				dq.push_back(j);
			}
		}
	}
	for(int i=0;i<=m;i++) ans=max(ans,f[1][i]);
	for(int v:edge[u]) {
		if(vis[v]) continue ;
		rt=0,sum=siz[v],get_root(v,u),solve(rt);
	}
	return ;
}

int main() {
	read(T),mxsiz[0]=inf;
	while(T--) {
		read(n,m),ans=0;
		for(int i=1;i<=n;i++) read(w[i]);
		for(int i=1;i<=n;i++) read(c[i]);
		for(int i=1;i<=n;i++) read(d[i]);
		for(int i=1;i<=n;i++) edge[i].clear(),vis[i]=0;
		for(int i=1,u,v;i<n;i++) read(u,v),edge[u].pb(v),edge[v].pb(u);
		rt=0,sum=n,get_root(1,0),solve(rt);
		write(ans),_E;
	}
	return 0;
}
posted @ 2025-07-28 16:47  LinkCatTree  阅读(9)  评论(0)    收藏  举报