博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UOJ 7 NOI2014 购票
阅读量:4676 次
发布时间:2019-06-09

本文共 7179 字,大约阅读时间需要 23 分钟。

题意:给一棵树计算一下各个点在距离限制下以一定的费用公式通过不停地到祖先最后到达一号点的最小花费。

第一种做法:线段树维护带修凸壳。显然的,这个公式计算是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
#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
#define pii pair
#define eps 1e-9#define inf 0x3f3f3f3fusing 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
sta[maxn*4];vector
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

  第二种做法:CDQ分治+点分治

在第一种做法中已经提到,就是算这条链对这个点的影响,而在链的时候,也就是一个区间对于这个点的影响,这种问题我们可以用CDQ分治加以解决(当然也可以平衡树...),然后应用到树上我们就用到了点分治这个东西来控制复杂度。其实我是写过点分治的题的。。就是先找下重心G,然后先递归处理掉fa[G]这颗子树的情况(因为G是真正的根)。处理完了之后就能计算这条链对于G的子树的贡献,把G的子树中满足条件的点拿出来,并按照最上方的点深度从高到低排序(倒着做),每次加入一个点维护凸壳,然后二分找到最优的位置,更新答案。注意这时要把这个点的最上方的点改成在dfs3中的新的top,因为那部分答案已经计算过了,相当于l~mid已经结束,剩下的是mid~r区间里左边的点对它的贡献(在G的子树中),否则复杂度也是不对的。点分治好像更快一些,但也是O(n*logn*logn),点分治一层log,二分一层log。

#include
#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
#define pii pair
#define eps 1e-9#define inf (((1ll<<62)-1)<<1)+1using 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])
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<
<<"\n"; //cout<
<<"\n"; dfs4(1); for(int i=2;i<=n;i++){printf("%lld\n",dp[i]);}}

 PS:其实这两个题要是不看别人的代码我恐怕要写到天荒地老,实在太菜了,当然比起去年在这种题面前想一下的能力都没有,现在起码那个线段树的做法还是比较显然的,点分治稍微磨炼一下估计更显然。怎么说呢,最近陆陆续续写了8、9个斜率优化的题,花了很多时间却也不能说很懂了。就CDQ分治其实最特别的代码还是她论文那个题,感觉比起什么陌上花开有意义多了。而这个题别人列举了7、8种做法,反正我也就这么稍微口糊一下,网上题解一大堆,但是我还有毅力去写这些题本身也是值得纪念的事情啊。 剩下的感触就在“有点xx”里写了。

转载于:https://www.cnblogs.com/intwentieth/p/10607005.html

你可能感兴趣的文章
单元测试之初识
查看>>
golang github.com/go-sql-driver/mysql 遇到的数据库,设置库设计不合理的解决方法
查看>>
内存分配 保存数据
查看>>
磁盘分区、格式化
查看>>
嵌入式实时操作系统的可裁剪性及其实现
查看>>
VC++2012编程演练数据结构《31》狄杰斯特拉算法
查看>>
盘点:移动服务 #AzureChat
查看>>
基于visual Studio2013解决C语言竞赛题之0608水仙花函数
查看>>
Sass学习笔记
查看>>
C语言练习
查看>>
Eclipse:An internal error occurred during: "Building workspace". GC overhead limit exceeded
查看>>
纯Css实现Div高度根据自适应宽度(百分比)调整
查看>>
jboss eap6.1(4)(部署应用)
查看>>
配置jboss EAP 6.4 数据库连接超时时间
查看>>
【BZOJ5005】乒乓游戏 [线段树][并查集]
查看>>
前端页面数据埋点、分析和参考
查看>>
NBear简介与使用图解
查看>>
ng-app一些使用
查看>>
ubuntu16.04安装 java JDK8
查看>>
中兴F412光猫超级密码破解、破解用户限制、关闭远程控制、恢复路由器拨号
查看>>