2021 ICPC 上海 H(Kruskal重构树)【Life is a Game】

题意:给一张无向图,每个结点具有点权,每条边有边权,有一个声望值,到达新的结点可以让声望值加上该点的点权,想要通过一条边需要满足现有的声望值大于该边的边权。给出多组询问,每次询问告诉你起始位置和初始声望值,问最多能有多少声望值。

数据范围:点数1e5,询问数1e5

样例输入:

8 10 2
3 1 4 1 5 9 2 6
1 2 7
1 3 11
2 3 13
3 4 1
3 6 31415926
4 5 27182818
5 6 1
5 7 23333
5 8 55555
7 8 37
1 7
8 30
样例输出:

16
36
 

分析:如果知道克鲁斯卡尔重构树的话,会发现题目中通过一条边的条件,需要现有的声望值大于边权,这个条件和克鲁斯卡尔重构树的性质,两个结点的LCA的权值为两点路径上最大边权,这个性质非常像,所以会考虑去建克鲁斯卡尔重构树,再接着考虑这棵树跟题意的关系。

↓样例对应的生成树长这样(做题时候的草稿,有点潦草,将就看看)

圆形的是实点,里面是序号,方形的是虚点,里面是路径上的最大权值。那么假如从8出发,能往上走两条边到达14,那么说明肯定也可以走过14左下的那些点10,5,6,所以能到达14,那此时的声望相当于14的子树中实点的权值和。那么我们就可以推导出能否通过一条边对初始权值的要求,mx[i]=i边父亲的虚点权值-i边连着的儿子的子树实点权值和。

又因为1e5次询问,肯定要考虑使用倍增来优化,就可以设置数组mx[i][j]表示从i结点出发往上走2^j条边,要求的最小初始声望值。而为了求出mx[i][0],我们需要维护每个结点的子树实点权值和,我用val[],表示,而重构树得到的限制边权虚点权值我用val2[]存储,就有了下面的代码 :

#include<bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
const int maxn=2e5+10;
int val[maxn<<1],fa[maxn<<1],f[maxn][25],dep[maxn],val2[maxn];
int n,m,q,lg[maxn],mx[maxn][25];//mx表示从结点i往上走2^j条边需要的初始声望值
vector<int>G[maxn];//存重构树的图
//val是点,val2是最大的边权
struct edge{
    int u,v,w;
}e[maxn<<1];
bool cmp(edge a,edge b){
    return a.w<b.w;
}
int Find(int x){return x==fa[x]?fa[x]:fa[x]=Find(fa[x]);}
void dfs(int u,int fu,int deep){
    dep[u]=deep;f[u][0]=fu;mx[u][0]=val2[fu]-val[u];
    for(int i=1;i<=lg[dep[u]];i++){
        f[u][i]=f[f[u][i-1]][i-1];
        mx[u][i]=max(mx[u][i-1],mx[f[u][i-1]][i-1]);
    }
	for(auto v:G[u])
        dfs(v,u,deep+1);
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    for(int i=2;i<maxn;i++) lg[i]=lg[i>>1]+1;
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++) cin>>val[i];
    for(int i=1;i<=2*n;i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        int u,v,w;cin>>u>>v>>w;
        e[i]=edge{u,v,w};
    }
    int cnt=n;
    sort(e+1,e+1+m,cmp);
    for(int i=1;i<=m;i++){
        int fu=Find(e[i].u),fv=Find(e[i].v),w=e[i].w;
        if(fu==fv) continue;
        cnt++;
        fa[fu]=cnt;fa[fv]=cnt;
        f[fu][0]=cnt;f[fv][0]=cnt;
        val2[cnt]=w;//val2是虚点的限制最大边权
        val[cnt]=val[fu]+val[fv];//val是子树的实点权值和
        G[cnt].push_back(fu);
        G[cnt].push_back(fv);
    }
    dfs(cnt,0,1);
    while(q--){
        int x,k;cin>>x>>k;
        for(int i=lg[dep[x]];i>=0;i--){
            if(k>=mx[x][i]&&f[x][i])
                x=f[x][i];
        }
        cout<<k+val[x]<<endl;
    }
    //system("pause");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值