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][ ...
随机推荐
- python基础之逻辑运算符
python逻辑运算符: ①and ‘与’ 总结: 如果and左边为False,则直接返回左边的结果(False) 如果and左边为True,则返回的结果取决于右边的数值 ②or ‘或’ 总结: 如果 ...
- java的设计模式 - 静态工厂方法
静态工厂方法,也不知道为何叫这个名字.其实也就是一个静态函数,可以替代构造函数用.大名鼎鼎的 guava 就大量使用这种模式,这是非常有用的模式. 比如是 Integer i = Integer.va ...
- Deepin MongoDB安装&使用总结
参考:手把手教你 MongoDB 的安装与详细使用(一) deepin 安装 mongodb 数据库(全面) 1. 导入公钥 sudo apt-key adv --keyserver hkp://ke ...
- jsp内置对象-config对象
1.概念:config对象中存储了一些Servlet初始化的数据结构,当Servlet初始化时,JSP容器通过config对象将这些信息传递给这个Servlet.一般在web.xml文件中配置Serv ...
- vue element-ui 文件上传
<el-upload class="upload-demo" action="" :before-remove="beforeRemove&qu ...
- 只需一行代码!Python中9大时间序列预测模型
在时间序列问题上,机器学习被广泛应用于分类和预测问题.当有预测模型来预测未知变量时,在时间充当独立变量和目标因变量的情况下,时间序列预测就出现了. 预测值可以是潜在雇员的工资或银行账户持有人的信用评分 ...
- 阿里云安装MySQL5.7
长话短说: step1:下载mysql源安装包:wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm ste ...
- EF6实现软删除
https://www.jianshu.com/p/c65fbfe16e1a
- animation动画案例
最近一直苦恼做一个banner的进度条,原先用js改变width值,但明显卡顿.后来用了animation,超级好用. <!DOCTYPE html> <html lang=&quo ...
- (light oj 1024) Eid (最小公倍数)
题目链接: http://lightoj.com/volume_showproblem.php?problem=1024 In a strange planet there are n races. ...