[杂题] hihocode1715. 树联通问题

考虑计算每条树边出现在哪些区间了,但是这样不太好统计,补集转换一下计算每条树边没有出现的区间的个数

那么用set维护一下每棵子树中的点的标号,如果一个区间里的元素都不在这个set里或者都在这个set里,那么这个点到父亲的边都不在这个区间里

启发式合并一下就可以了

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <set>
#define fi first
#define se second

using namespace std;

typedef long long ll;
typedef pair<int,int> pii;

const int N=100010;

int n,cnt,G[N]; ll w[N];
struct edge{
    int t,nx;
}E[N<<1];

set<pii> S[N];

inline void addedge(int x,int y){
    E[++cnt].t=y; E[cnt].nx=G[x]; G[x]=cnt;
    E[++cnt].t=x; E[cnt].nx=G[y]; G[y]=cnt;
}

inline ll C(int l,int r){
    return 1LL*(r-l+1)*(r-l)/2+r-l+1;
}

inline void ins(int x,pii y){
    set<pii> &c=S[x];
    int l,r;
    if(c.empty() || *c.begin()>y) l=1;
    else l=(--c.upper_bound(y))->se+1;
    if(c.empty() || *c.rbegin()<y) r=n;
    else r=c.upper_bound(y)->fi-1;

    w[x]-=C(l,r);
    if(l<y.fi) w[x]+=C(l,y.fi-1);
    else{
        if(!c.empty() && *c.begin()<y){
            set<pii>::iterator it=c.upper_bound(y); it--;
            y.fi=it->fi; w[x]-=C(it->fi,it->se); c.erase(it);
        }
    }
    if(r>y.se) w[x]+=C(y.se+1,r);
    else{
        if(!c.empty() && *c.rbegin()>y){
            set<pii>::iterator it=c.upper_bound(y);
            y.se=it->se; w[x]-=C(it->fi,it->se); c.erase(it);
        }
    }
    w[x]+=C(y.fi,y.se);
    c.insert(y);
}

void dfs(int x,int f){
    w[x]=C(1,n); ins(x,pii(x,x));
    for(int i=G[x];i;i=E[i].nx)
        if(E[i].t!=f){
            dfs(E[i].t,x);
            if(S[E[i].t].size()>S[x].size())
                swap(S[E[i].t],S[x]),w[x]=w[E[i].t];
            for(set<pii>::iterator it=S[E[i].t].begin();it!=S[E[i].t].end();it++) ins(x,*it);   
        }
}

int main(){
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
    scanf("%d",&n);
    for(int i=1,x,y;i<n;i++)
        scanf("%d%d",&x,&y),addedge(x,y);
    dfs(1,0);
    ll ans=0;
    for(int i=2;i<=n;i++) ans+=1LL*n*(n+1)/2-w[i];
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值