zoukankan      html  css  js  c++  java
  • UOJ#284. 快乐游戏鸡

    I.V.UOJ#284. 快乐游戏鸡

    我们来思考一下你游戏的过程:每次找到一个最浅的 (w_i) 大于当前死亡次数的位置 (i),走到那儿;不断这样,直到下面两件事中有一件先发生:

    • 你当前的死亡次数允许你一路走到终点。
    • 你的死亡次数已经不小于 (w_i),需要寻找新的 (i)

    然后我们来思考这一过程,发现如果将子树中所有节点按深度排序,只访问那些 (w_i) 是前缀最大值的节点一定构成一组最优解。而这明显可以用一个单调栈处理。

    又因为我们要合并父亲与儿子的单调栈,就可以启发式合并做到 (O(nlog n)) 或是选择长链剖分做到 (O(n))。介于这里是长链剖分学习笔记,选择长链剖分。

    现在来思考如何求解。设我们当前考虑了从 (x)(y) 的路径。首先,”死亡次数允许一路走到终点“,等价于死亡次数到达路径最大值,设为 (z)。假设单调栈中元素是 ((dep_1,w_1),(dep_2,w_2),dots,(dep_k,w_k)),则我们显然只需要截出 (wleq z) 的一段前缀 (1sim p),将这段前缀全部走完后,因为 (w_{p+1}>z),所以我们只需不断走 (p+1) 直到死亡次数达到 (z) 即可。

    找到前缀可以使用单调栈上二分。找到路径最大值可以树上倍增。

    然后我们考虑计算总路径长度。其等于 (Big(sumlimits_{i=1}^p(w_i-w_{i-1})dep_i+(z-w_{p})dep_{p+1}Big)+Big(dep_y-dep_xBig)-zdep_x)。其中各部分间已经用大括号分割好了。

    最前面那个括号中的 (sum) 显然不能 (O(n)) 遍历单调栈计算,须要预处理。但是单调栈只能处理从栈底出发的后缀和,不能处理从栈顶出发的前缀和。但注意到本题的信息具有可减性,于是直接维护后缀和,然后用栈顶值减去 (p+1) 位置的值即可。

    单调栈为了能够二分须保证下标连续,所以使用了指针维护。时间复杂度 (O(nlog n))

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,m,a[300100],fa[300100],dep[300100],dpt[300100],son[300100],anc[300100][20],st[300100][20],tot;
    ll res[300100];
    vector<int>v[300100];
    vector<pair<int,int> >u[300100];
    struct node{int a,d;ll sum;}p[300100],*h[300100],*t[300100],stk[300100],*tp;
    void insert(int x,node y){//insert y into the array of x
    	node *&H=h[x],*&T=t[x];
    	while(H<=T&&H->a<=y.a)H++;//y ought to have a smaller d than all elements in x, so just pop.
    	if(H>T||H->d>y.d)y.sum=(H<=T?1ll*(H->a-y.a)*H->d+H->sum:0),*--H=y;
    }
    void merge(int x,int y){//merge the array of y into that of x.
    	node*&hx=h[x],*&tx=t[x],*&hy=h[y],*&ty=t[y];
    	tp=stk;while(hx<=tx&&hx->d<ty->d)*++tp=*hx++;//insert the can-be-deleted elements into an accessory array,leaving those must-saving elements
    	while(tp>stk&&hy<=ty)insert(x,ty->d>tp->d?*ty--:*tp--);//insert the one with greater d, to make the'insert'operation valid.
    	while(tp>stk)insert(x,*tp--);while(ty>=hy)insert(x,*ty--);//the same way like normal merging.
    }
    int doubling(int x,int y){
    	int ret=0;
    	for(int i=19;i>=0;i--)if(anc[y][i]&&dep[anc[y][i]]>dep[x])ret=max(ret,st[y][i]),y=anc[y][i];
    	return ret;
    }
    ll solve(int x,int y){
    	if(x==y)return 0;int z=doubling(x,y);
    //	printf("(%d,%d,%d)
    ",x,y,z);
    	if(h[x]->a>=z)return 1ll*(h[x]->d-dep[x])*z+dep[y]-dep[x];//just simply go to the first element of the array for z times.
    	node *l=h[x],*r=t[x],*mid;//the first elememt is inadequate, need to binary search for the proper one. 
    	while(l<r)(mid=(l+(r-l)/2))->a>=z?r=mid:l=mid+1;
    	return 1ll*l->d*(z-l->a)-1ll*dep[x]*z+(h[x]->sum-l->sum)+1ll*h[x]->a*h[x]->d+dep[y]-dep[x];
    }
    void dfs(int x){
    	++tot;if(son[x])dfs(son[x]),h[x]=h[son[x]],t[x]=t[son[x]];else h[x]=p+tot,t[x]=h[x]-1;
    	for(auto y:v[x])if(y!=son[x])dfs(y),merge(x,y);
    	for(auto i:u[x])res[i.second]=solve(x,i.first);
    	insert(x,(node){a[x],dep[x],0});
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    	for(int i=2;i<=n;i++)scanf("%d",&fa[i]),dep[i]=dep[fa[i]]+1,v[fa[i]].push_back(i),anc[i][0]=fa[i],st[i][0]=a[fa[i]];
    	for(int j=1;j<=19;j++)for(int i=1;i<=n;i++)anc[i][j]=anc[anc[i][j-1]][j-1],st[i][j]=max(st[i][j-1],st[anc[i][j-1]][j-1]);
    	dpt[0]=-1;for(int i=n;i>=2;i--){dpt[i]=dpt[son[i]]+1;if(dpt[i]>dpt[son[fa[i]]])son[fa[i]]=i;}
    	scanf("%d",&m);
    	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),u[x].push_back(make_pair(y,i));
    	dfs(1);
    	for(int i=1;i<=m;i++)printf("%lld
    ",res[i]);
    	return 0;
    }
    

  • 相关阅读:
    HTML DOM教程 14HTML DOM Document 对象
    HTML DOM教程 19HTML DOM Button 对象
    HTML DOM教程 22HTML DOM Form 对象
    HTML DOM教程 16HTML DOM Area 对象
    ubuntu 11.04 问题 小结
    VC6.0的 错误解决办法 小结
    boot.img的解包与打包
    shell里 截取字符串
    从零 使用vc
    Imagemagick 对图片 大小 和 格式的 调整
  • 原文地址:https://www.cnblogs.com/Troverld/p/14637024.html
Copyright © 2011-2022 走看看