• 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;
    }
    

  • 相关阅读:
    C#预编译指令
    net 数据库连接详解 相当经典啊
    C#中参数前缀ref、out的使用
    C#中HTML字符转换函数
    SQL语句【TSQL汇总】
    64位XP操作系统下访问Access数据库的问题及解决
    ASP.NET Cache的一些总结
    xml 总结
    POJ 1011 Sticks
    POJ 3278 Catch That Cow
  • 原文地址:https://www.cnblogs.com/Troverld/p/14637024.html
Copyright © 2020-2023  润新知