Flash by sshockwave [树dp]
题目
给定一棵树,每个点有一个活动时间,长度为正整数$t_i$
你需要安排每个点的活动时间什么时候开始什么时候结束,并且满足:任何一个时刻没有两个相邻的点都在活动
开始时刻为0,在以上条件下最小化所有点的结束时间之和
$n \leq 2000$
思路
首先,给定的所有$t_i$都是正整数,说明答案一定是整数(这虽然很显然,但是很重要)
考虑某一个点什么时候开始
显然,最优的情况下它的开始时间可以被安排到和自己某个相邻点的结束时间相邻(或者它自己是在开始时刻就开始的)
又考虑到隔壁点也满足这一条,所以对于这个点来说,它的前驱时间(也就是在开始前的等待时间)一定可以被表示为从这个点开始的一条树链的时间和
注意上面的“可以被表示为”这样的表述:不是说只能这么安排,是说这样安排是可行的,而且方便我们设计算法
对于每个点$u$,计算数组$dis[u]$,表示排序去重后的从$u$出发的所有树链的长度
然后设$dp[u][i]$表示从根节点开始$dp$,在当前节点的子树中得到的结果:$dp[u][i]$表示在$u$开始之前已经经过了$dis[u][i]$这么长的等待时间
注意这里的$dp$并没有考虑父亲那边,而是只考虑了子树部分
因此在递归回到父亲那里做转移的时候,要这样操作:
t1[0]=t2[cnt[v]+1]=1e15;
for(i=1;i<=cnt[v];i++) t1[i]=min(t1[i-1],dp[v][i]);
for(i=cnt[v];i>=1;i--) t2[i]=min(t2[i+1],dp[v][i]);
l=0;r=0;
for(i=1;i<=cnt[u];i++){
while(dis[v][l+1]+w[v]<=dis[u][i]) l++;
while(dis[v][r]<dis[u][i]+w[u]) r++;
dp[u][i]+=min(t1[l],t2[r]);
}
做一个前后缀最小值,然后两边双指针进去搞到范围,然后再转移
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#define ll long long
using namespace std;
inline int read(){
int re=0,flag=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') flag=-1;
ch=getchar();
}
while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
int n,first[10010],cnte=-1;
struct edge{
int to,next;
}a[20010];int w[10010];
inline void add(int u,int v){
a[++cnte]=(edge){v,first[u]};first[u]=cnte;
a[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
ll dis[2010][2010],lis[2010],dp[2010][2010];
int root,cnt[2010];
void getlis(int u,int f,ll d){
lis[++cnt[root]]=d;
// cout<<"getlis "<<u<<' '<<root<<' '<<d<<'\n';
int i,v;
for(i=first[u];~i;i=a[i].next){
v=a[i].to;if(v==f) continue;
getlis(v,u,d+w[v]);
}
}
void init(){
memset(first,-1,sizeof(first));
int i,num,t1,t2;
n=read();
for(i=1;i<n;i++){
t1=read();t2=read();
add(t1,t2);
}
for(i=1;i<=n;i++) w[i]=read();
for(i=1;i<=n;i++){
root=i;getlis(i,0,0);
sort(lis+1,lis+cnt[i]+1);
cnt[i]=unique(lis+1,lis+cnt[i]+1)-lis-1;
memcpy(dis[i]+1,lis+1,sizeof(ll)*(cnt[i]+1));
dis[i][0]=-1e15;
dis[i][cnt[i]+1]=1e15;
}
}
void dfs(int u,int f){
int i,v,j,l,r;
for(i=1;i<=cnt[u];i++) dp[u][i]=dis[u][i]+w[u];
for(i=first[u];~i;i=a[i].next){
v=a[i].to;if(v==f) continue;
dfs(v,u);
}
for(j=first[u];~j;j=a[j].next){
v=a[j].to;if(v==f) continue;
static ll t1[2010],t2[2010];
t1[0]=t2[cnt[v]+1]=1e15;
for(i=1;i<=cnt[v];i++) t1[i]=min(t1[i-1],dp[v][i]);
for(i=cnt[v];i>=1;i--) t2[i]=min(t2[i+1],dp[v][i]);
l=0;r=0;
for(i=1;i<=cnt[u];i++){
while(dis[v][l+1]+w[v]<=dis[u][i]) l++;
while(dis[v][r]<dis[u][i]+w[u]) r++;
dp[u][i]+=min(t1[l],t2[r]);
}
}
}
int main(){
init();
dfs(1,0);
int i;ll ans=1e15;
for(i=1;i<=cnt[1];i++) ans=min(ans,dp[1][i]);
cout<<ans<<'\n';
}
Flash by sshockwave [树dp]的更多相关文章
- CF456D A Lot of Games (字典树+DP)
D - A Lot of Games CF#260 Div2 D题 CF#260 Div1 B题 Codeforces Round #260 CF455B D. A Lot of Games time ...
- HDU4916 Count on the path(树dp??)
这道题的题意其实有点略晦涩,定义f(a,b)为 minimum of vertices not on the path between vertices a and b. 其实它加一个minimum ...
- Codeforces 219D. Choosing Capital for Treeland (树dp)
题目链接:http://codeforces.com/contest/219/problem/D 树dp //#pragma comment(linker, "/STACK:10240000 ...
- HDU4276 The Ghost Blows Light SPFA&&树dp
题目的介绍以及思路完全参考了下面的博客:http://blog.csdn.net/acm_cxlove/article/details/7964739 做这道题主要是为了加强自己对SPFA的代码的训练 ...
- Tsinsen A1219. 采矿(陈许旻) (树链剖分,线段树 + DP)
[题目链接] http://www.tsinsen.com/A1219 [题意] 给定一棵树,a[u][i]代表u结点分配i人的收益,可以随时改变a[u],查询(u,v)代表在u子树的所有节点,在u- ...
- HDU 3016 Man Down (线段树+dp)
HDU 3016 Man Down (线段树+dp) Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Ja ...
- bzoj 3572世界树 虚树+dp
题目大意: 给一棵树,每次给出一些关键点,对于树上每个点,被离它最近的关键点(距离相同被标号最小的)控制 求每个关键点控制多少个点 分析: 虚树+dp dp过程如下: 第一次dp,递归求出每个点子树中 ...
- bzoj 2286 [Sdoi2011]消耗战 虚树+dp
题目大意:多次给出关键点,求切断边使所有关键点与1断开的最小费用 分析:每次造出虚树,dp[i]表示将i和i子树与父亲断开费用 对于父亲x,儿子y ①y为关键点:\(dp[x]\)+=\(dismn( ...
- (纪念第一道完全自己想的树DP)CodeForces 219D Choosing Capital for Treeland
Choosing Capital for Treeland time limit per test 3 seconds memory limit per test 256 megabytes inpu ...
随机推荐
- django 面试题
面试题1:migrate怎么判断哪些迁移脚本需要执行: 他会将代码中的迁移脚本和数据库中django_migrations中的迁移脚本进行对比,如果发现数据库中,没有这个迁移脚本,那么就会执行这个迁移 ...
- Centos6 Ruby 1.8.7升级至Ruby 2.3.1的方法
本文章地址:https://www.cnblogs.com/erbiao/p/9117018.html#现在的版本 [root@hd4 /]# ruby --version ruby (-- patc ...
- YII2.O学习三 前后台用户数据表分离
之前我们完成了Advanced 模板安装,也完成了安装adminlte 后台模板,这一步是针对前端和后台用户使用不同的数据库表来管理,做到前后台用户分离的效果: 复制一张user数据表并重命名为adm ...
- 接口API封装中常见的HTTP状态码
在进行后端接口API封装的过程中,需要考虑各种错误信息的输出.一般情况下,根据相应问题输出适合的HTTP状态码,可以方便前端快速定位错误,减少沟通成本. HTTP状态码有很多,每个都有对应的含义,下面 ...
- Python练习笔记(2)
文件读写,多线程.多进程 import time,os,threading,random def file_read(path): try: with open(path, 'r') as f: # ...
- 怎么修复网站漏洞之metinfo远程SQL注入漏洞修补
2018年11月23日SINE网站安全检测平台,检测到MetInfo最新版本爆出高危漏洞,危害性较大,影响目前MetInfo 5.3版本到最新的 MetInfo 6.1.3版本,该网站漏洞产生的主要原 ...
- Windows10 快捷键
windows 10快捷键: F1 打开帮助 F2 重命名 F3 打开搜索文件和文件夹 F4 打开地址栏常用的地址 F5 刷新 F11 全屏 选择文件和内容: shift + 上下左右键选择连续的 ...
- MyBatis的笔记
1.#{}和${}的区别是什么? #{}是预编译处理,${}是字符串替换. #{}是sql的参数占位符,${}是Properties文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替 ...
- python eval()函数的妙用和滥用
eval()函数十分强大,官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果: >>> s='8*8' >>> eval(s) 64 >& ...
- 修改mysql root密码的方法
方法1: 用SET PASSWORD命令 首先登录MySQL. 格式:mysql> set password for 用户名@localhost = password('新密码'); 例子:my ...