首页 > 其他分享 >【题解】UOJ#284 快乐游戏鸡

【题解】UOJ#284 快乐游戏鸡

时间:2024-05-31 22:22:02浏览次数:14  
标签:int 题解 fro deep stk fa UOJ son 284

题目大意

给出一棵有 \(n\) 个节点的树,编号为 \(i\) 的点权为 \(w_i\),在树上通过一条边需要花费时间 \(1\),如果能通过一个点权为 \(w_i\) 的点当且仅当此时的死亡次数大于等于 \(w_i\),否则会立即回到起点并且死亡次数加一。给出 \(q\) 组询问,每组询问给出起点 \(s\) 和终点 \(t\),且 \(s\) 一定是 \(t\) 的父亲,只能往 \(s\) 的子树内走。问最后从 \(s\) 走到 \(t\) 至少需要多长时间(不需要考虑 \(s\),\(t\) 上的点权)。

分析

有个很直觉的思路,直接头铁从 \(s\) 向 \(t\) 莽,离线下来统计答案即可,这样是链的特殊性质分。

但是稍微看下样例就知道这个想法是错的,并且样例启发我们是从 \(s\) 往一些深度较浅的点走,快速积累死亡次数后再去冲 \(t\)。因此我们考虑将 \(s\) 的子树内的点按照深度从浅到深排序,对于同一个深度只需要保留 \(w_i\) 最大的那个点;并且积累的死亡次数刚刚好够通过 \(s->t\) 的路径一定是优的。接下来我们再考虑怎么处理这两个问题(维护 \(s\) 的子树,统计答案)。

假设存在两个点 \(i\),\(j\),并且 \(deep_i<deep_j\),\(w_i>w_j\),那我们一定可以通过 \(i\) 积累到最多 \(w_i\) 的死亡次数,而这个一定比 \(j\) 优,所以我们根本不需要考虑 \(j\)。因此若我们考虑维护一个单调栈,栈内的元素一定是按照 \(w\) 和深度递增的。从深到浅插入点,假设当前插入的点是 \(i\),那么栈内元素的深度一定都大于 \(i\),如果 \(w_i\) 还大于 \(w_{top}\),那么 \(top\) 一定是一个没用的值,直接弹掉。

但是这样要么只能在线 \(O(nq)\) 做,要么就得离线对每个点都开一个栈,空间也不支持。而我们发现 \(fa_i\) 和 \(i\) 存储的栈内实际上所有 \(i\) 子树内的元素都是重复的,大大浪费了空间。所以我们考虑将查询离线下来,维护好 \(i\) 的栈后统计所有 \(s=i\) 的查询的答案,然后 \(i\) 的栈就直接丢给 \(fa_i\) 合并就好。

考虑怎么合并。用启发式合并的思想,显然是把 \(i\) 合并到 \(fa_i\) 时间复杂度更优,并且 \(fa_i\) 可以直接继承它长儿子的栈,时间复杂度就正确了。合并时只会影响 \(fa_i\) 栈内深度小于 \(i\) 中最深深度的元素。把这些元素拿出来,和 \(i\) 按照深度从小到大,类似于归并排序一样把元素一个一个重新插进 \(fa_i\) 的栈。但是这样除非一些释放空间的操作,要不然空间复杂度还是不对。借鉴 hzy 博客 里面说的队列启发式合并,我们考虑将长队列按照 \([dfn_i,dfn_i+siz_i-1]\) 的空间分配。这样合并的时候就可以重复利用空间。

最后考虑怎么统计答案。首先考虑到统计答案的时候我们要在单调栈所对应的树上的点之间跑来跑去,直接暴力走显然时间复杂度不对。考虑相邻两个 \(i\) 和 \(i+1\) 之间的贡献就是 \(deep_{i+1}(w_{i+1}-w_i)\)。这样就可以维护一个从栈底到当前元素的后缀和了。对于点 \(s\),刚刚好能从 \(s\) 到 \(t\) 的死亡次数一定会是这条路径上的点权最大值 \(W\),这个可以用树链剖分+线段树或者树上倍增处理掉。然后在它的单调栈中二分找到最大的满足 \(w_l<=W\) 的 \(l\)。最后的答案就是单调栈之间转移的贡献 \(sum_{top}-sum_l+deep_{top}\times w_{top}\) 再加上让死亡次数从 \(w_l\) 到 \(W\) 的贡献 \((W-w_l)\times deep_{l+1}\),最后由于栈里维护的深度是假装起点为根节点的,每多死一次就会多跑 \(deep_s\),所以最后还要减去 \(deep_s\times w\),再加上死亡次数达到 \(W\) 后从 \(s\) 到 \(t\) 的时间 \(deep_t-deep_s\)。注意特判一下单调栈中根本没有 \(\le W\) 的情况。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int n;
int a[N];
int head[N],tot;

struct node{
    int nex,to;
}edge[N];

void add(int x,int y){
    edge[++tot].nex=head[x];
    edge[tot].to=y;
    head[x]=tot;
}

int fa[N][25];
int deep[N],maxdep[N];
int son[N];
int maxi[N][25];

void pre(int x,int fx){
    deep[x]=deep[fx]+1;
    fa[x][0]=fx;
    maxi[x][0]=a[fx];
    for(int i=1;i<=20;++i){
        fa[x][i]=fa[fa[x][i-1]][i-1];
        maxi[x][i]=max(maxi[x][i-1],maxi[fa[x][i-1]][i-1]);
    }
    for(int i=head[x];i;i=edge[i].nex){
        int v=edge[i].to;
        if(v==fx) continue;
        pre(v,x);
        if(maxdep[v]>maxdep[son[x]]) son[x]=v;
    }
    maxdep[x]=maxdep[son[x]]+1;
}

int query(int x,int y){
    int nmaxi=0;
    for(int i=20;i>=0;--i){
        if(deep[fa[y][i]]>deep[x]){
            nmaxi=max(nmaxi,maxi[y][i]);
            y=fa[y][i];
        }
    }
    return nmaxi;
}

vector<pair<int,int> >que[N];

struct Node{
    int deep,w,sum;
}stk[N],ttk[N];

int dfn[N],dn;
int fro[N],tai[N];

void insert(int x,Node k){//insert k into x
    while(fro[x]<=tai[x]&&stk[fro[x]].w<=k.w) fro[x]++;
    if(fro[x]>tai[x]||stk[fro[x]].deep>k.deep){
        stk[fro[x]-1].deep=k.deep;
        stk[fro[x]-1].w=k.w;
        stk[fro[x]-1].sum=0;
        if(fro[x]<=tai[x]){
            stk[fro[x]-1].sum=stk[fro[x]].sum+(stk[fro[x]].w-k.w)*stk[fro[x]].deep;
        }
        fro[x]--;
    }
}

int top;

void unionn(int x,int y){//unionn y into x
    top=0;
    while(fro[x]<=tai[x]&&stk[fro[x]].deep<stk[tai[y]].deep){
        ttk[++top]=stk[fro[x]];
        fro[x]++;
    }

    while(top&&fro[y]<=tai[y]){
        if(ttk[top].deep>stk[tai[y]].deep){
            insert(x,ttk[top]);
            top--;
        }
        else{
            insert(x,stk[tai[y]]);
            tai[y]--;
        }
    }
    while(fro[y]<=tai[y]){
        insert(x,stk[tai[y]]);
        tai[y]--;
    }
    while(top){
        insert(x,ttk[top]);
        top--;
    }
}

int ansi[N];

void solve(int x,int id){
    int w=query(x,que[x][id].first);
    int l=fro[x]-1,r=tai[x]+1;
    while(l+1<r){
        int mid=(l+r)>>1;
        if(stk[mid].w<=w) l=mid;
        else r=mid;
    }

    int sum=0;
    if(stk[fro[x]].w>w) sum=w*stk[fro[x]].deep-deep[x]*w;
    else sum=stk[fro[x]].sum-stk[l].sum+stk[fro[x]].deep*stk[fro[x]].w+(w-stk[l].w)*stk[l+1].deep-deep[x]*w;
    sum+=deep[que[x][id].first]-deep[x];
    ansi[que[x][id].second]=sum;
}

void dfs(int x,int fx){
    dfn[x]=++dn;
    if(son[x]){
        dfs(son[x],x);
        fro[x]=fro[son[x]];
        tai[x]=tai[son[x]];
    }
    else{
        fro[x]=dn;
        tai[x]=dn-1;
    }

    for(int i=head[x];i;i=edge[i].nex){
        int v=edge[i].to;
        if(v==fx||v==son[x]) continue;
        dfs(v,x);
        unionn(x,v);
    }

    for(int i=0;i<que[x].size();++i) solve(x,i);
    insert(x,Node{deep[x],a[x]});
}

signed main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i];
    for(int i=2;i<=n;++i){
        int x;
        cin>>x;
        add(x,i);
    }
    pre(1,0);
    int m;
    cin>>m;
    for(int i=1;i<=m;++i){
        int s,t;
        cin>>s>>t;
        pair<int,int>tmp={t,i};
        que[s].push_back(tmp);
    }
    dfs(1,0);
    for(int i=1;i<=m;++i) cout<<ansi[i]<<"\n";
    return 0;
}

总结

本题需要分析题目性质得到对于每个点用单调栈维护信息的解法,并需要灵活运用队列启发式合并等启发式算法降低时间复杂度,个人感觉本题还是不错的。

标签:int,题解,fro,deep,stk,fa,UOJ,son,284
From: https://www.cnblogs.com/Cloote/p/18225353

相关文章

  • CF1981D题解
    CF1981D题解前言标签:筛法,欧拉回路。赛后过的,构造一眼秒,欧拉图写错了,多少有点抽象。题意构造一个长度为\(n\)的序列\(a\),需要满足:\(\forall1\lei\len\),\(1\lea_i\le3\times10^5\)。\(\forall1\lei<j<n\),\(a_i\timesa_{i+1}\nea_j\timesa_{j+1}\)。......
  • UOJ#884. 【UR #27】509 号迷宫
    有一个显然的\(\mathcalO(n^2)\)DP。考虑利用组合数优化,只在满足纵坐标\(y|p\)的位置记录状态并转移。有障碍,需要做容斥。四种转移:线对线、点对点、线对点、点对线组合计数算明白了就简单了。代码#include<bits/stdc++.h>usingnamespacestd;constexprintN=......
  • 题解合集
    CF1270FAwesomeSubstringsCF1860CGameonPermutationP10161[DTCPC2024]小方的疑惑10P10236[yLCPC2024]D.排卡P10368「LAOI-4」ColorsP10369「LAOI-4」MexTower(Easyver.)P10370「LAOI-4」MexTower(Hardver.)P2398GCDSUMP2568GCDP8445射命丸文的取材......
  • CF Round946(div3) 题解
    目录946(div3)ABCDEG946(div3)A每个屏幕3X5,可放2个2X2,其余可填7个1X1先算2X2需要多少个屏幕,再算当前屏幕是否可放下所有1X1,根据1X1的量加屏幕.voidfunc(){ inta,b; cin>>a>>b; intans=(b+1)/2; intstp=a-(ans*15-4*b); if(stp>0) ans+=(s......
  • P2215 [HAOI2007] 上升序列题解
    题目大意对于一个集合$S$,对于$S$中长度为$m$的子序列$P$,在集合$P$中如果$P_1<P_2<...<P_m$那么我们称$P$为$S$的一个上升序列。如果有多个$P$满足条件我们就输出最小的那个,如果没有完成条件的$P$则输出Impossible。思路对于一个含有$......
  • P2266 爱的供养题解
    题目大意给你一个矩阵$w$,大小为$n\timesm$,然后你每次都从一个宝藏点开始去走旁边$T-1$个点施法,施法过的点就不用再走了,施法不需要耗费体力但是每一次从一个点走到另一个点需要耗费的权值为这两个点的高度差,你每次可以走的方向是上下左右。求最小需要耗费的体力。......
  • P10526 [XJTUPC2024] 命令行题解
    题目大意对于一个字符串$s$在输入的最后一行读入的字符,如个字符不为$E\(,\)T\(,\)B$那么这一个字符就添加至字符串$s$的末尾。对于操作$B$那么执行删除字符串$s$的最后一个字符,如果$s$为空那么跳过这个操作。对于操作$T$找到一个以字符串......
  • P10530 [XJTUPC2024] 生命游戏 题解
    题目大意一棵树一共$n$个点如果有$k$个点与某一个点相连那么这一轮的结尾这个点就会死。思路这道题有几个坑!并没有说哪一个节点是根节点。双向边记得开双倍数组。等这一轮的点消除完了才能再次判断哪一些点可以消除。首先我们创建一个数组$Size_{n}$来......
  • CF/505/D 题解
    思路这道题的思路其实是根据样例图片来的。首先第一张:这张图片可以得知$n$个点没有环的时候最少需要$n-1$个点。再看第二张:这个图确实很难思考,但稍微思考一下如果我们把含有一个强连通分量的图变成一整个强连通图。这个图的边数不就变成$n$了吗?来推一下......
  • P6342 [CCO2017] Vera 与道路建设 题解
    题目大意对于一个图w一共有v个点点的编号为1,2,...,v,对于点a与点b如果满足$a\tob$且$b\toa$使得每一条道路都只走过一次,那么我们称$a,b$为完美点对,当一个联通图只有$k$个完美点对时,称这个联通图为美丽公路网,要求求出一个美丽公路网......