[洛谷U40581]树上统计treecnt

[洛谷U40581]树上统计treecnt

题目大意:

给定一棵\(n(n\le10^5)\)个点的树。

定义\(Tree[l,r]\)表示为了使得\(l\sim r\)号点两两连通,最少需要选择的边的数量。

\(\sum_{l=1}^n\sum_{r=l}^nTree[l,r]\)

思路:

对于每个边考虑贡献,若我们将出现在子树内的点记作\(1\),出现在子树外的点记作\(0\),那么答案就是\(\frac{n(n-1)}2-\)\(0\)、全\(1\)串的个数。线段树合并,维护前缀/后缀最长全\(0\)/全\(1\)串即可。

时间复杂度\(\mathcal O(n\log n)\)

源代码:

#include<cstdio>
#include<cctype>
#include<vector>
inline int getint() {
    register char ch;
    while(!isdigit(ch=getchar()));
    register int x=ch^'0';
    while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
    return x;
}
typedef long long int64;
const int N=1e5+1,logN=18;
int n;
int64 ans;
std::vector<int> e[N];
inline void add_edge(const int &u,const int &v) {
    e[u].push_back(v);
    e[v].push_back(u);
}
inline int64 calc(const int &n) {
    return 1ll*n*(n-1)/2;
}
struct Node {
    int pre[2],suf[2],len;
    int64 sum;
    Node() {}
    Node(const int &l,const bool &v) {
        pre[!v]=suf[!v]=0;
        pre[v]=suf[v]=len=l;
        sum=calc(l);
    }
    friend Node operator + (const Node &l,const Node &r) {
        Node ret;
        ret.pre[0]=l.pre[0]+r.pre[0]*(l.pre[0]==l.len);
        ret.pre[1]=l.pre[1]+r.pre[1]*(l.pre[1]==l.len);
        ret.suf[0]=r.suf[0]+l.suf[0]*(r.suf[0]==r.len);
        ret.suf[1]=r.suf[1]+l.suf[1]*(r.suf[1]==r.len);
        ret.len=l.len+r.len;
        ret.sum=l.sum+r.sum+1ll*l.suf[0]*r.pre[0]+1ll*l.suf[1]*r.pre[1];
        return ret;
    }
};
class SegmentTree {
    #define mid ((b+e)>>1)
    private:
        Node node[N*logN];
        int left[N*logN],right[N*logN];
        int sz,new_node() {
            return ++sz;
        }
        int len(const int &b,const int &e) {
            return e-b+1;
        }
        void push_up(const int &p,const int &b,const int &e) {
            if(!left[p]) node[p]=Node(len(b,mid),0)+node[right[p]];
            if(!right[p]) node[p]=node[left[p]]+Node(len(mid+1,e),0);
            if(left[p]&&right[p]) {
                node[p]=node[left[p]]+node[right[p]];
            }
        }
    public:
        int root[N];
        void insert(int &p,const int &b,const int &e,const int &x) {
            if(!p) p=new_node();
            if(b==e) {
                node[p]=Node(1,1);
                return;
            }
            if(x<=mid) insert(left[p],b,mid,x);
            if(x>mid) insert(right[p],mid+1,e,x);
            push_up(p,b,e);
        }
        void merge(int &p,const int &q,const int &b,const int &e) {
            if(!p||!q) {
                p=p|q;
                return;
            }
            if(b==e) return;
            merge(left[p],left[q],b,mid);
            merge(right[p],right[q],mid+1,e);
            push_up(p,b,e);
        }
        int64 query(const int &p) const {
            return node[p].sum;
        }
    #undef mid
};
SegmentTree t;
void dfs(const int &x,const int &par) {
    t.insert(t.root[x],1,n,x);
    for(auto &y:e[x]) {
        if(y==par) continue;
        dfs(y,x);
        t.merge(t.root[x],t.root[y],1,n);
    }
    if(x!=1) ans-=t.query(t.root[x]);
}
int main() {
    n=getint();
    ans=calc(n)*(n-1);
    for(register int i=1;i<n;i++) {
        add_edge(getint(),getint());
    }
    dfs(1,0);
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/skylee03/p/9832585.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值