15059:特教的理性愉悦

问题 C: 特教的理性愉悦
时间限制: 1 Sec 内存限制: 128 MB

题目描述
某正教授级特级教师忽然想自娱自乐。于是他准备在纸上画一棵树。这棵树开始时只有N个点,然后特教开始逐条画上一些带权无向边(保证任意时刻纸上的任意两点间至多有一条路径),直到最后形成一棵树。
为了达到理性愉悦的目的,特教在加边的过程中可能会随意选取两个点并取出连接它们的路径,计算该路径上所有点对距离之和(如果已经有路径连通)。
例如路径共有(1,2)(1,3)(1,4)(2,3)(2,4)(3,4)六个点对,距离之和为5+8+10+3+5+2=33。现在特教需要检验他的答案是否正确,于是想请你编个程序帮他验算一下。
输入
第一行为两个正整数,N,M(N≤100000,M≤250000),表示树的点数和操作数。
第二行开始的M行为M个操作的具体内容,分为两种:
(1)“1 u v w”,表示加一条连接u和v的边,边权为w;
(2)“2 u v”,表示询问当前树上u到v的路径上所有点对距离之和,如果还不连通则输出-1。
输入数据保证加边合法,u≠v,边权w为正整数,且其中恰有N-1个(1)操作以及至少1个(2)操作,即M≥N。
输出
对于每个(2)操作输出一行,表示询问的结果。
样例输入 Copy
【样例1】
4 5
1 1 2 5
1 2 3 3
2 1 4
1 3 4 2
2 1 4
【样例2】
10 18
1 2 4 456
1 9 5 801
2 7 3
1 9 7 473
1 9 2 591
1 7 6 409
2 4 2
1 1 7 997
1 3 4 751
1 6 8 576
1 4 10 657
2 4 9
2 2 9
2 9 6
2 4 3
2 10 9
2 7 4
2 9 8
样例输出 Copy
【样例1】
-1
33
【样例2】
-1
456
2094
591
1764
751
5568
5151
4783

思路

首先,由于形成了一棵树,因此只要询问时可达,此时询问答案和所有边连完再询问答案是相同的,因此拿一个并查集维护是否可达,如果不可达标记一下,所有边连完后统一处理剩下的答案
然后就是答案处理,可以看出,答案是一段路径上每个边权分别乘不同的系数,系数可以如下得到:

第x个1234
-1
-12
-123
-1234
sum4664

换一种计算方式:

第x个1234
-1234
-1234
-1234
-1234
-1234
sum4664
被删除的和14916
总和5*1=55*2=105*3=155*4=20

也就是说,对于一条链,如果处理出来边权为149和123的sum,那么答案就是(len+1)*sum123-sum149
由于这个权值一定是对称的,计u,v的lca为p,则可以分别处理u-p,v-p两段再求和。其中计算答案时长度len要用总长度,每一段只截取前d项,d为到lca的深度差。

然后就是如何维护和截取149和123了,记正常的sum为111,那么我们就可以通过前一项来推导
维护:

sum111[p]=sum111[now]+x;
sum123[p]=sum123[now]+sum111[p];
sum149[p]=sum149[now]+sum123[now]*2+sum111[p];

截取:

sum123[u]-sum123[p]-sum111[p]*d;
sum149[u]-(sum149[p]+sum123[p]*2*d+sum111[p]*d*d);

代码

#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>

typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int N=3e5+5;
const int M=(1<<16)+5;
const int mod=19260817;
//const ll inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double eps=1e-5;
const double pi=acos(-1);
//const int tmp=31;
typedef pair<int,__int128>pii;

const int maxh=40;
vector<pii>g[N];
__int128 sum111[N],sum123[N],sum149[N];
int anc[N][40],dep[N];
void dfs(int now,int fa){
    if(now!=1){
        for(int i=1;i<maxh;i++){
            int y=anc[now][i-1];
            anc[now][i]=anc[y][i-1];
        }
    }
    for(auto it:g[now]){
        if(fa==it.first) continue;
        int p=it.first;
        __int128 x=it.second;
        sum111[p]=sum111[now]+x;
        sum123[p]=sum123[now]+sum111[p];
        sum149[p]=sum149[now]+sum123[now]*2+sum111[p];

        dep[p]=dep[now]+1;
        anc[p][0]=now;

        dfs(it.first,now);
    }
}
void swim(int &x,int H){
    for(int i=0;H>0;i++){
        if(H&1) x=anc[x][i];
        H/=2;
    }
}
int lca(int x,int y){
    int i;
    if(dep[x]>dep[y]) swap(x,y);
    swim(y,dep[y]-dep[x]);
    if(x==y) return x;
    for(;;){
        for(i=0;anc[x][i]!=anc[y][i];i++);
        if(i==0) return anc[x][0];
        x=anc[x][i-1];
        y=anc[y][i-1];
    }
    return -1;
}

__int128 res[N];
int tot;
int f[N],uu[N],vv[N];
int getf(int x){
    if(f[x]==x) return x;
    return f[x]=getf(f[x]);
}
void print(__int128 x){
    if(x>=10) print(x/10);
    cout<<(int)(x%10);
}
int main() {
#ifdef DEBUG
    freopen("in", "r", stdin);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0);

    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        f[i]=i;
    }
    while(m--){
        int op;
        cin>>op;
        if(op==1){
            int u,v;
            ll w;
            cin>>u>>v>>w;
            g[u].push_back({v,w});
            g[v].push_back({u,w});
            f[getf(u)]=getf(v);
        }
        else {
            int u,v;
            cin>>u>>v;
            ++tot;
            if(getf(u)!=getf(v)) res[tot]=-1;
            else uu[tot]=u,vv[tot]=v;
        }
    }
    dfs(1,0);
    for(int i=1;i<=tot;i++)
    {
        if(res[i]!=-1){
            int u=uu[i],v=vv[i];
            int p=lca(u,v);
            __int128 d=dep[u]-dep[p];
            __int128 len=(dep[u]-dep[p]+dep[v]-dep[p]+1);
            __int128 ans=(sum123[u]-sum123[p]-sum111[p]*d)*len;
            ans-=sum149[u]-(sum149[p]+sum123[p]*2*d+sum111[p]*d*d);
            d=dep[v]-dep[p];
            ans+=(sum123[v]-sum123[p]-sum111[p]*d)*len;
            ans-=sum149[v]-(sum149[p]+sum123[p]*2*d+sum111[p]*d*d);
            res[i]=ans;
        }
        print(res[i]);
        cout<<'\n';
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值