zoukankan      html  css  js  c++  java
  • LOJ 6192 城市网络(树上倍增)

    LOJ #6192. 「美团 CodeM 复赛」城市网络(链接)

    一棵以 $ 1 $ 号节点为根的树,每个点有一个权值,有 $ q $ 个询问,每次从 $ x $ 点开始往某个祖先 $ y $ 走,初始有权值 $ c $ ,如果路径上遇到更大的权值,那么 $ c $ 改为那个权值,问会修改多少次。数据范围: $ nleq 2 imes 10^5 $



    $ solution: $

    首先因为本题没有修改操作,所以可以离线维护。然后我们发现如果我们在 $ x $ 处节点被修改权值,那么问题从这个节点开始就等效于:从 $ x $ 节点以其本身权值为初始权值向根节点走。然后此题还有另一个突破口:每一个节点,若以当前节点权值为初始权值,它向根节点走第一个会被修改的节点是确定的!这是倍增的标志!!!

    结合上面两个性质,我们可以想出一种做法:用倍增数组 $ f[200005][19] $ 维护祖辈里比他大的节点(第一个比他大的,第二个,第四个,八个........)。如果我们初始权值为 $ v $ ,那么我们只要找到第一个比他大的节点(这个也可以用倍增完成),然后我们在这个节点开始用倍增,因为数组里记录都是比当前节点大的节点,所以我们只需将数组里所有深度小于终点的节点数记录下来即可!

    至于预处理,首先我们虚拟一个根节点的父亲,权值无限大。然后更新某个节点处置时,找到父辈里第一个比他大的节点,用这个节点更新当前节点即可。具体寻找时:我们要从父亲节点出发,因为数组里记录的是比他大的节点,我们只需要看看那个节点的权值是否大于当前节点,小于等于才跳,最后得到的节点的父亲即为所求!



    $ code: $

    #include<iostream>
    #include<cstdio>
    #include<iomanip>
    #include<algorithm>
    #include<cstring>
    #include<cstdlib>
    #include<ctime>
    #include<cmath>
    #include<vector>
    #include<queue>
    #include<map>
    #include<set>
    
    #define ll long long
    #define db double
    #define rg register int
    
    using namespace std;
    
    int n,q;
    int top;
    int a[400005]; //节点权值
    int dp[400005]; //节点深度
    int f[300005][19]; //树上倍增
    // f数组存的是祖辈里比他大的节点(第一个比他大的,第二个,第四个,八个........)
    
    struct su{
    	int to,next;
    }b[800005];
    int tou[400005]; //链式前向星
    
    inline int qr(){
    	register char ch; register bool sign=0; rg res=0;
    	while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    	while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    	if(sign)return -res; else return res;
    }
    
    inline int get(int x,int v){ //找到去根节点的路径上第一个比他大的
    	if(a[x]>v)return x; //x是他父亲
    	for(rg i=18;i>=0;--i){
    		if(a[f[x][i]]<=v)x=f[x][i]; //只有都小于等于他才向上跳,倍增套路
    	}x=f[x][0]; return x; //小于等于才跳,那么下一个就是第一个比他大的
    }
    
    inline void dfs(int i,int fa){
    	dp[i]=dp[fa]+1; //深度
    	rg x=get(fa,a[i]); f[i][0]=x; //当前节点还没有值,从父亲开始找
    	for(rg j=0;j<18;++j)
    		f[i][j+1]=f[f[i][j]][j]; //用第一个比他大的节点更新倍增信息
    	for(rg j=tou[i];j;j=b[j].next)
    		if(b[j].to!=fa)dfs(b[j].to,i);
    }
    
    inline int ask(int x,int y,int v){
    	for(rg i=18;i>=0;--i)
    		if(a[f[x][i]]<=v)x=f[x][i]; //和get函数差不多,找到第一个比他大的
    	if(a[x]<=v)x=f[x][0]; //有可能初始节点就比v大
    	if(dp[x]<dp[y])return 0; //说明没有比他大的
    	rg res=1;
    	for(rg i=18;i>=0;--i){
    		if(dp[f[x][i]]>=dp[y])res+=1<<i,x=f[x][i]; //按照深度一点一点逼近
    	}return res;
    }
    
    int main(){
    	n=qr(); q=qr();
    	for(rg i=1;i<=n;++i) a[i]=qr();
    	for(rg i=1;i<n;++i){
    		rg x=qr(),y=qr();
    		b[++top].to=y; b[top].next=tou[x]; tou[x]=top; //居然不开c++11
    		b[++top].to=x; b[top].next=tou[y]; tou[y]=top; // o(一︿一+)o
    	} a[0]=1e9; dfs(1,0); //根节点的父亲要赋最大!!!
    	for(rg i=1;i<=q;++i){
    		rg x=qr(),y=qr(),z=qr();
    		printf("%d
    ",ask(x,y,z));
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    【洛谷P1330】封锁阳光大学
    【洛谷P1087】FBI树
    hdu 4504(动态规划)
    hdu 4503(数学,概率)
    hdu 5400(思路题)
    hdu 5701(区间查询思路题)
    hdu 4502(DP)
    hdu 1401(单广各种卡的搜索题||双广秒速)
    hdu 1258(DFS)
    hdu 1254(搜索题)
  • 原文地址:https://www.cnblogs.com/812-xiao-wen/p/11296110.html
Copyright © 2011-2022 走看看