[BZOJ4182]Shopping (点分治+树上多重背包+单调队列优化)
[BZOJ4182]Shopping (点分治+树上多重背包+单调队列优化)
题面
马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街。商店街有n个商店,并且它们之间的道路构成了一颗树的形状。
第i个商店只卖第i种物品,小苗对于这种物品的喜爱度是wi,物品的价格为ci,物品的库存是di。但是商店街有一项奇怪的规定:如果在商店u,v买了东西,并且有一个商店w在u到v的路径上,那么必须要在商店w买东西。小葱身上有m元钱,他想要尽量让小苗开心,所以他希望最大化小苗对买到物品的喜爱度之和。这种小问题对于小葱来说当然不在话下,但是他的身边没有电脑,于是他打电话给同为OI选手的你,你能帮帮他吗?
分析
由题意,如果在u,v买了东西,那u到v的路径上的所有点一定都得买。因此最后购买东西的点一定是一个联通块。显然这个联通块一定包含某个子树的重心,所以可以点分治。
当我们点分治到某个重心rt的时候,我们考虑对这个重心分治出来子树计算背包。显然子树中的一个节点被选,它的父亲也一定被选。那么我们就把问题转化成了一般的树形背包问题,注意结点x必须选。
那么就可以列出模板的dp方程。设\(dp[i][j]\)表示当前处理到dfs序为i的节点x,当前背包内物品价格不大于j,能得到的最大喜爱度。设x子树的下一个子树dfs序的开始位置为nex[x].x为dfs序i对应的节点
则\(dp[i][j]=max(dp[nex[x]][j])\)(不选子树x)
\(dp[i][j]=max(dp[i+1][j-k \times w[x]]+k*c[x])\)(在x号节点买东西,买k个)
我们倒序dp是因为这样\(dp[0][m]\)存储的就是包含重心rt,且价格不大于m的最大喜爱度。可以直接用来更新答案
第二个方程显然可以按照多重背包的方法单调队列优化,这里不再赘述
总时间复杂度\(O(nm\log n)\)
代码
//http://119.29.55.79/contest/88/problem/2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500
#define maxv 4000
using namespace std;
int T,n,m;
struct edge{
int from;
int to;
int next;
}E[maxn*2+5];
int esz=0;
int head[maxn+5];
void add_edge(int u,int v){
esz++;
E[esz].from=u;
E[esz].to=v;
E[esz].next=head[u];
head[u]=esz;
}
int totsz,root;
bool vis[maxn+5];
int sz[maxn+5];
int maxsz[maxn+5];
void get_root(int x,int fa){
sz[x]=1;
maxsz[x]=0;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa&&!vis[y]){
get_root(y,x);
sz[x]+=sz[y];
maxsz[x]=max(maxsz[x],sz[y]);
}
}
maxsz[x]=max(maxsz[x],totsz-sz[x]);
if(maxsz[x]<maxsz[root]) root=x;
}
int tim;
int dfn[maxn+5],hash_dfn[maxn+5];
int nex[maxn+5];
void get_dfn(int x,int fa){
dfn[x]=tim++;
hash_dfn[dfn[x]]=x;
sz[x]=1;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa&&!vis[y]){
get_dfn(y,x);
sz[x]+=sz[y];
}
}
nex[x]=tim;
}
int q[maxv+5];
int w[maxn+5],c[maxn+5],d[maxn+5];
int dp[maxn+5][maxv+5];
int ans=0;
void solve(int rt){
vis[rt]=1;
tim=0;
get_dfn(rt,0);
//一定要选根节点
//倒序循环,这样dp[0]就存储了选rt的答案
memset(dp[tim],0,sizeof(dp[tim]));
for(int i=tim-1;i>=0;i--){
int x=hash_dfn[i];
for(int j=0;j<=m;j++) dp[i][j]=dp[nex[x]][j];//不选
for(int b=0;b<c[x];b++){//选,多重背包单调队列优化
int head=1,tail=0;
for(int a=0;a*c[x]+b<=m;a++){
while(head<=tail&&a-q[head]>d[x]) head++;
if(head<=tail) dp[i][a*c[x]+b]=max(dp[i][a*c[x]+b],dp[i+1][q[head]*c[x]+b]+(a-q[head])*w[x]);
int now=dp[i+1][a*c[x]+b]-a*w[x];
while(head<=tail&&now>=dp[i+1][q[tail]*c[x]+b]-q[tail]*w[x]) tail--;
q[++tail]=a;
}
}
}
for(int i=1;i<=m;i++) ans=max(ans,dp[0][i]);
for(int i=head[rt];i;i=E[i].next){
int y=E[i].to;
if(!vis[y]){
root=0;
maxsz[root]=sz[y];
totsz=sz[y];
get_root(y,rt);
solve(root);
}
}
}
void ini(){
esz=0;
memset(head,0,sizeof(head));
ans=0;
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
memset(sz,0,sizeof(sz));
memset(maxsz,0,sizeof(maxsz));
memset(dfn,0,sizeof(dfn));
memset(hash_dfn,0,sizeof(hash_dfn));
memset(nex,0,sizeof(nex));
memset(q,0,sizeof(q));
}
int main(){
// freopen("1.in","r",stdin);
int u,v;
scanf("%d",&T);
while(T--){
ini();
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<=n;i++) scanf("%d",&c[i]);
for(int i=1;i<=n;i++) scanf("%d",&d[i]);
for(int i=1;i<n;i++){
scanf("%d %d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
root=0;
maxsz[root]=n;
totsz=n;
get_root(1,0);
solve(root);
printf("%d\n",ans);
}
}
[BZOJ4182]Shopping (点分治+树上多重背包+单调队列优化)的更多相关文章
- BZOJ 4182 Shopping (点分治+树上多重背包)
题目大意:给你一颗树,你有$m$元钱,每个节点都有一种物品,价值为$w$,代价为$c$,有$d$个,如果在$u$和$v$两个城市都购买了至少一个物品,那么$u,v$路径上每个节点也都必须买至少一个物品 ...
- 【POJ1276】Cash Machine(多重背包单调队列优化)
大神博客转载http://www.cppblog.com/MatoNo1/archive/2011/07/05/150231.aspx多重背包的单调队列初中就知道了但一直没(不会)写二进制优化初中就写 ...
- Luogu 3423 [POI 2005]BAN-银行票据 (多重背包单调队列优化 + 方案打印)
题意: 给出 n 种纸币的面值以及数量,求最少使用多少张纸币能凑成 M 的面额. 细节: 好像是要输出方案,看来很是头疼啊. 分析: 多重背包,裸体??? 咳咳,好吧需要低调,状态就出来了: dp [ ...
- poj1742 Coins(多重背包+单调队列优化)
/* 这题卡常数.... 二进制优化或者单调队列会被卡 必须+上个特判才能过QAQ 单调队列维护之前的钱数有几个能拼出来的 循环的时候以钱数为步长 如果队列超过c[i]就说明队头的不能再用了 拿出来 ...
- hdu 2844 多重背包+单调队列优化
思路:把价值看做体积,而价值的大小还是其本身,那么只需判断1-m中的每个状态最大是否为自己,是就+1: #include<iostream> #include<algorithm&g ...
- BZOJ.4182.Shopping(点分治/dsu on tree 树形依赖背包 多重背包 单调队列)
BZOJ 题目的限制即:给定一棵树,只能任选一个连通块然后做背包,且每个点上的物品至少取一个.求花费为\(m\)时最大价值. 令\(f[i][j]\)表示在点\(i\),已用体积为\(j\)的最大价值 ...
- bzoj 1531 Bank notes 多重背包/单调队列
多重背包二进制优化终于写了一次,注意j的边界条件啊,疯狂RE(还是自己太菜了啊啊)最辣的辣鸡 #include<bits/stdc++.h> using namespace std; in ...
- POJ 1742 Coins(多重背包, 单调队列)
Description People in Silverland use coins.They have coins of value A1,A2,A3...An Silverland dollar. ...
- 多重背包 /// 单调队列DP oj1943
题目大意: em.... 就是多重背包 挑战340页的东西 ...自己的笔记总结总是比较乱的 重点:原始的状态转移方程中 更新第 i 种物品时 重量%w[i] 的值不同 则它们之间是相互独立的: 1- ...
随机推荐
- 使用git管理文件版本
创建版本库 什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改.删除,Git都能跟踪,以便任何时刻都可以 ...
- osi7层模型及线程和进程
端口的作用: 在同一台电脑上,为了让不同 的程序分离开来! http:网站默认端口是80 https:网站默认端口是443 osi七层模型: 1.应用层:软件 2.表示层:接收数据 3.会话:保持登录 ...
- java:Set对象TreeSet有序子类,HashSet无序子类,重复对象二
TreeSet有序子类; HashSet无序子类 重复重复元素,Object对象是通过equals和hashCode来进行过滤的. 如果将上一篇提到中的例子中的TreeSet,换成HashSet,那么 ...
- SpringBoot框架(4)-- 类装配及Bean装配监听器
1.普通方式装配类对象 (1)添加带有@Bean注解的方法 User.java(带@Component注解) package com.demo.boot.bootenable.beanDemo1 ...
- postman—Sandbox和断言
Postman沙盒 Postman Sandbox是一个JavaScript执行环境,您可以在编写预请求脚本和测试脚本(在Postman和Newman中)时可用.在这个沙箱中执行您在预请求/测试脚本部 ...
- Bugku 杂项 telnet
letnet 下载zip后发现是一个数据包,放到wireshark中打开 右键追踪tcp流后可以发现flag
- nginx之Geoip读取地域信息模块
1 geoip_module模块 基于IP地址匹配MaxMind GeolP二进制文件,读取IP所在地域信息. yum install nginx-module-geoip geoip2已经有了,安装 ...
- (3.2)狄泰软件学院C++课程学习剖析三
对课程前面40课的详细回顾分析(一) 0. int main() { // ① Array t(3,3); //普通模式 // ② Array *t=new Array(3,3); //指针方式 // ...
- 3D Computer Grapihcs Using OpenGL - 03 OpenGL Buffer Data
本节绘制一个三角形,并讲解Buffer Object-缓冲对象 OpenGL的窗口坐标 屏幕中心为坐标原点,横向朝右为x正方向,纵向朝上为y正方向,最大值最小值分别为1,-1. Buffer Obje ...
- 用HTML5 Geolocation实现一个距离追踪器
HTML5 Geolocation(地理定位)用于定位用户的位置.那么如何实现一个距离追踪器呢?我的思路是这样的,前提是浏览器支持h5地理定位,在这个基础上,获取用户位置,更新用户位置,计算距离,显示 ...