UOJ 7 NOI2014 购票
题意:给一棵树计算一下各个点在距离限制下以一定的费用公式通过不停地到祖先最后到达一号点的最小花费。
第一种做法:线段树维护带修凸壳。显然的,这个公式计算是p*x+q 所以肯定和斜率有关系。然后这题的dp方程也是非常显然的,dp[x]=min(dp[y]+(dis[x]-dis[y])*p[x]+q[x]) ,其中y是x的祖先,并且dis[x]-dis[y]<=l[x]。然后这个式子稍微划一下就能推出单调性,以及以(dis[x],dp[x])这样子的点的形式,求最小值那么下凸壳。很自然地想到这个是个从上往下的dp,也就是x这个点到祖先的这条链会对x的答案产生影响。那么继续非常直觉的想法就是dfs这颗树每次维护这条链的凸壳。然而这个时候直接维护单调栈是有问题的,因为距离的限制,导致有些本来不会是单调栈中的点在某次询问时可能是最优的点。而其实我们每次对于一个点的答案因为距离的限制也就成为了一段区间,那么也就是要维护这条链上的区间,自然地想到线段树,对于每个线段树的节点开一个凸包,这样单次修改最多修改logn个节点,对于插入一个点要二分一下最左边满足条件的位置,然后记录一下历史版本,即之前该位置的值以及凸壳的siz。然后dfs完这个点后就可以O(1)还原了。就做到了O(logn)插入,O(1)还原。然后第一次写这种题的我发现这个写起来很奇怪,因为大多数时候二分的log是跑不满的。然后更新dp时只要框出区间,然后在线段树上查询这个区间就好了。复杂度O(n*logn*logn)。
#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define ls (x<<1)
#define rs (x<<1|1)
#define db double
#define all(x) x.begin(),x.end()
#define ll long long
#define pll pair<ll,ll>
#define pii pair<int,int>
#define eps 1e-9
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=2e5+100;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
} int n,t;
int d_top;
ll dp[maxn],s[maxn],p[maxn],q[maxn],l[maxn],d_sta[maxn],d[maxn]; vector<int>sta[maxn*4];
vector<int>vec[maxn];
int top[maxn*4],pre[maxn][20],last[maxn][20],seg_d[maxn*4],fa[maxn],id[maxn];
void build(int x,int l,int r)
{
seg_d[x]=seg_d[x>>1]+1;
sta[x].resize(r-l+3);
if(l==r) return;
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
bool xl(int i,int j,int k)
{
return 1.0*(dp[j]-dp[i])*(d[k]-d[j])>=1.0*(dp[k]-dp[j])*(d[j]-d[i]);
}
int find_pos(int x,int i)
{
if(!top[x])return 1;
int l=1,r=top[x]-1,res=top[x]+1;
while(l<=r)
{
int mid=(l+r)>>1;
if(xl(sta[x][mid],sta[x][mid+1],i)){
r=mid-1;res=mid+1;
}
else l=mid+1;
}return res;
}
void push(int x,int i)
{
int pl=find_pos(x,i);
last[i][seg_d[x]]=top[x];
pre[i][seg_d[x]]=sta[x][pl];
top[x]=pl;
sta[x][pl]=i;
}
void back(int x)
{
int i=sta[x][top[x]];
sta[x][top[x]]=pre[i][seg_d[x]];
top[x]=last[i][seg_d[x]];
}
ll cal(int x,int y)
{
return dp[x]+(d[y]-d[x])*p[y]+q[y];
}
bool cmp(int x,int y,ll val)
{
return dp[y]-dp[x]<=val*(d[y]-d[x]);
}
ll ask(int x,int pl)
{
int l=1,r=top[x]-1,res=1;
while(l<=r)
{
int mid=(l+r)>>1;
if(cmp(sta[x][mid],sta[x][mid+1],p[pl])){
l=mid+1;res=mid+1;
}
else{
r=mid-1;
}
}
return cal(sta[x][res],pl);
}
void insert(int x,int l,int r,int pl,int o)
{
if(o)push(x,id[pl]);
else back(x);
if(l==r)return;
int mid=(l+r)>>1;
if(pl<=mid)insert(ls,l,mid,pl,o);
else insert(rs,mid+1,r,pl,o);
}
ll query(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return ask(x,id[R+1]);
int mid=(l+r)>>1;
ll res=4e18;
if(L<=mid)res=query(ls,l,mid,L,R);
if(R>mid)res=min(res,query(rs,mid+1,r,L,R));
return res;
}
void dfs(int x)
{
d_sta[++d_top]=d[x]=d[fa[x]]+s[x];id[d_top]=x;
if(x!=1){
int st=lower_bound(d_sta+1,d_sta+d_top+1,d[x]-l[x])-d_sta;
dp[x]=query(1,1,n,st,d_top-1);
}
insert(1,1,n,d_top,1);
for(int i=0;i<vec[x].size();i++){
dfs(vec[x][i]);
}
insert(1,1,n,d_top,0);
d_top--;
}
int main()
{
n=read();t=read();
for(int i=2;i<=n;i++)
{
fa[i]=read();s[i]=read();p[i]=read();q[i]=read();l[i]=read();
vec[fa[i]].pb(i);
}
build(1,1,n);
dfs(1);
//cout<<222<<"\n";
for(int i=2;i<=n;i++)
{
cout<<dp[i]<<"\n";
}
}
第二种做法:CDQ分治+点分治
在第一种做法中已经提到,就是算这条链对这个点的影响,而在链的时候,也就是一个区间对于这个点的影响,这种问题我们可以用CDQ分治加以解决(当然也可以平衡树...),然后应用到树上我们就用到了点分治这个东西来控制复杂度。其实我是写过点分治的题的。。就是先找下重心G,然后先递归处理掉fa[G]这颗子树的情况(因为G是真正的根)。处理完了之后就能计算这条链对于G的子树的贡献,把G的子树中满足条件的点拿出来,并按照最上方的点深度从高到低排序(倒着做),每次加入一个点维护凸壳,然后二分找到最优的位置,更新答案。注意这时要把这个点的最上方的点改成在dfs3中的新的top,因为那部分答案已经计算过了,相当于l~mid已经结束,剩下的是mid~r区间里左边的点对它的贡献(在G的子树中),否则复杂度也是不对的。点分治好像更快一些,但也是O(n*logn*logn),点分治一层log,二分一层log。
#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define ls (x<<1)
#define rs (x<<1|1)
#define db double
#define all(x) x.begin(),x.end()
#define ll long long
#define ldb long double
#define pll pair<ll,ll>
#define pii pair<int,int>
#define eps 1e-9
#define inf (((1ll<<62)-1)<<1)+1
using namespace std;
const int maxn=2e5+100;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int top[maxn],siz[maxn],fa[maxn][20],bel[maxn],st[maxn],vis[maxn],maxx[maxn],p[maxn];
int cnt,ntop,tot;
int n,T;
int fir[maxn],nxt[maxn*2],to[maxn*2];
ll f[maxn],s[maxn],q[maxn],l[maxn],dep[maxn],dis[maxn],dp[maxn];
bool cmp(const int &a,const int &b)
{
return dep[top[a]]>dep[top[b]];
}
ll Max(ll a,ll b){
return a>b?a:b;
}
ll Min(ll a,ll b)
{
return a>b?b:a;
}
struct node
{
ll x,y;
}no[maxn];
void add_e(int x,int y)
{
++cnt;nxt[cnt]=fir[x];to[cnt]=y;fir[x]=cnt;
}
void dfs(int x)
{
dep[x]=dep[fa[x][0]]+1;
for(int i=0;i<17;i++)fa[x][i+1]=fa[fa[x][i]][i];
for(int i=fir[x];i;i=nxt[i]){
int tt=to[i];dis[tt]=dis[x]+s[tt];dfs(tt);
}
}
int bz(int x,ll lim)
{
int tt=x;
for(int i=17;i>=0;i--)
{
if(fa[x][i]&&dis[tt]-dis[fa[x][i]]<=lim) x=fa[x][i];
}
return x;
}
void dfs1(int x)
{
siz[x]=1;maxx[x]=0;
for(int i=fir[x];i;i=nxt[i]){
if(!vis[to[i]])
{
dfs1(to[i]);siz[x]+=siz[to[i]];maxx[x]=Max(maxx[x],siz[to[i]]);
}
}
}
void dfs2(int rt,int x,int &G)
{
if(Max(maxx[x],siz[rt]-siz[x])<Max(maxx[G],siz[rt]-siz[G]))G=x;
for(int i=fir[x];i;i=nxt[i]){if(!vis[to[i]])dfs2(rt,to[i],G);}
}
int calg(int x)
{
int G=x;
dfs1(x);dfs2(x,x,G);return G;
}
double xl(node x,node y)
{
return 1.0*(y.y-x.y)/(1.0*(y.x-x.x));
}
void ins(node x)
{
while(ntop>1&&xl(x,no[ntop])>=xl(no[ntop],no[ntop-1]))ntop--;
no[++ntop]=x;
}
ll query(ll val)
{
int l=1,r=ntop-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(no[mid+1].y-no[mid+1].x*val-(no[mid].y-no[mid].x*val)>=0){
r=mid-1;
}else{
l=mid+1;
}
}
return no[l].y-no[l].x*val;
}
void dfs3(int rt,int x,int maxd)
{
if(dep[top[x]]<=maxd){
st[++tot]=x;bel[tot]=rt;
}
for(int i=fir[x];i;i=nxt[i])if(!vis[to[i]])dfs3(rt,to[i],maxd);
}
void dfs4(int x)
{
int G=calg(x);vis[G]=1;
if(x!=G)
{
dfs4(x);
int v=fa[G][0];
while(dep[v]>=dep[x]&&dep[v]>=dep[top[G]])
{
dp[G]=Min(dp[G],dp[v]+(dis[G]-dis[v])*p[G]+q[G]);v=fa[v][0];
}
}
tot=ntop=0;
for(int i=fir[G];i;i=nxt[i])
{
if(!vis[to[i]])
dfs3(to[i],to[i],dep[G]);
}
sort(st+1,st+1+tot,cmp);
int v=G;
for(int i=1;i<=tot;i++)
{
int tt=st[i];
while(dep[v]>=dep[top[tt]])
{
ins((node){dis[v],dp[v]});v=fa[v][0];
}
dp[tt]=Min(dp[tt],query(p[tt])+dis[tt]*p[tt]+q[tt]);
top[tt]=bel[i]; } for(int i=fir[G];i;i=nxt[i])if(!vis[to[i]])dfs4(to[i]);
}
int main()
{
n=read();T=read();
for(int i=2;i<=n;i++)
{
fa[i][0]=read();s[i]=read();p[i]=read();q[i]=read();l[i]=read();dp[i]=inf;
add_e(fa[i][0],i);
}
dfs(1);
for(int i=1;i<=n;i++)top[i]=bz(i,l[i]);//,cout<<top[i]<<"\n";
//cout<<dep[fa[1][0]]<<"\n";
dfs4(1);
for(int i=2;i<=n;i++){printf("%lld\n",dp[i]);}
}
PS:其实这两个题要是不看别人的代码我恐怕要写到天荒地老,实在太菜了,当然比起去年在这种题面前想一下的能力都没有,现在起码那个线段树的做法还是比较显然的,点分治稍微磨炼一下估计更显然。怎么说呢,最近陆陆续续写了8、9个斜率优化的题,花了很多时间却也不能说很懂了。就CDQ分治其实最特别的代码还是她论文那个题,感觉比起什么陌上花开有意义多了。而这个题别人列举了7、8种做法,反正我也就这么稍微口糊一下,网上题解一大堆,但是我还有毅力去写这些题本身也是值得纪念的事情啊。 剩下的感触就在“有点xx”里写了。
UOJ 7 NOI2014 购票的更多相关文章
- [BZOJ3672][UOJ#7][NOI2014]购票
[BZOJ3672][UOJ#7][NOI2014]购票 试题描述 今年夏天,NOI在SZ市迎来了她30周岁的生日.来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会. ...
- UOJ#7 NOI2014 购票 点分治+凸包二分 斜率优化DP
[NOI2014]购票 链接:http://uoj.ac/problem/7 因为太麻烦了,而且暴露了我很多学习不扎实的问题,所以记录一下具体做法. 主要算法:点分治+凸包优化斜率DP. 因为$q_i ...
- UOJ #7 NOI2014购票(点分治+cdq分治+斜率优化+动态规划)
重写一遍很久以前写过的题. 考虑链上的问题.容易想到设f[i]为i到1的最少购票费用,转移有f[i]=min{f[j]+(dep[i]-dep[j])*p[i]+q[i]} (dep[i]-dep[j ...
- [BZOJ3671][UOJ#6][NOI2014]随机数生成器
[BZOJ3671][UOJ#6][NOI2014]随机数生成器 试题描述 小H最近在研究随机算法.随机算法往往需要通过调用随机数生成函数(例如Pascal中的random和C/C++中的rand)来 ...
- [BZOJ3670][UOJ#5][NOI2014]动物园
[BZOJ3670][UOJ#5][NOI2014]动物园 试题描述 近日,园长发现动物园中好吃懒做的动物越来越多了.例如企鹅,只会卖萌向游客要吃的.为了整治动物园的不良风气,让动物们凭自己的真才实学 ...
- bzoj 3672: [Noi2014]购票 树链剖分+维护凸包
3672: [Noi2014]购票 Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 480 Solved: 212[Submit][Status][D ...
- BZOJ 3672: [Noi2014]购票( 树链剖分 + 线段树 + 凸包 )
s弄成前缀和(到根), dp(i) = min(dp(j) + (s(i)-s(j))*p(i)+q(i)). 链的情况大家都会做...就是用栈维护个下凸包, 插入时暴力弹栈, 查询时就在凸包上二分/ ...
- bzoj千题计划251:bzoj3672: [Noi2014]购票
http://www.lydsy.com/JudgeOnline/problem.php?id=3672 法一:线段树维护可持久化单调队列维护凸包 斜率优化DP 设dp[i] 表示i号点到根节点的最少 ...
- [BZOJ3672][Noi2014]购票 斜率优化+点分治+cdq分治
3672: [Noi2014]购票 Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 1749 Solved: 885[Submit][Status][ ...
随机推荐
- 碰到了通过Movie显示gif图片,有部分图片的duration为0导致gif只显示第一帧
解决办法,改为使用android-gif-drawable.jar来显示gif图片(需要配合com.android.support:support-v4:18.0.0使用) GifImageView ...
- Oracle 常用Sql 语句
Oracle数据库常常被用作项目开发的数据库之一:有时隔段时间没使用就会忘记一些常用的sql语法,所以我们有必要记录下常用的sql 语句,当我们需要时可以快速找到并运用. 1 创建表空间.创建用户及授 ...
- 用存储过程向数据库添加大量数据【mysql】
预分配ID的设计,需要先为数据库生成大量的数据.比如对用户ID有要求的系统,那么用户ID就要预先生成. 通过python,php,c/c++/c#,js等程序生成也是可以,但需要这些程序环境,而且单条 ...
- rocketmq4.x快速入门指南
以下采用的是apache rocketmq 4.2.0版本 相关文档如下 快速体验: http://blog.seoui.com/2018/07/24/rocketmqinstall/ rocketm ...
- 好代码是管出来的——.Net Core中的单元测试与代码覆盖率
测试对于软件来说,是保证其质量的一个重要过程,而测试又分为很多种,单元测试.集成测试.系统测试.压力测试等等,不同的测试的测试粒度和测试目标也不同,如单元测试关注每一行代码,集成测试关注的是多个模块是 ...
- 查看SQL Server服务运行帐户和SQL Server的所有注册表项
查看SQL Server服务运行帐户和SQL Server的所有注册表项 SELECT * FROM sys.dm_server_registry SELECT * FROM sys.dm_serve ...
- linux安装windows启动盘
安装gparted
- C# ComboBox绑定值问题
使用这种方式始终绑定值有问题: cbxSchool.DataSource = schoolList; cbxSchool.DisplayMember = "school_name" ...
- JDBC获取数据库连接
是什么? JDBC:Java Data Base Connectivity(java数据库连接) 为什么用? sun公司提供JDBC API接口,数据库厂商来提供实现 我们需要用哪个数据库就加载那个数 ...
- 简单介绍python的双向队列
介绍 大家都知道利用 .append 和 .pop 方法,我们可以把列表当作栈或者队列来用(比如,把 append 和 pop(0) 合起来用,就能模拟栈的“先进先出”的特点).但是删除列表的第一个元 ...