树上统计treecnt(dsu on tree 并查集 正难则反)

题目链接

\(Description\)

给定一棵\(n(n\leq 10^5)\)个点的树。
定义\(Tree[L,R]\)表示为了使得\(L\sim R\)号点两两连通,最少需要选择的边的数量。
求\[\sum_{l=1}^n\sum_{r=l}^nTree[l,r]\]

\(Solution\)

枚举每条边,计算它的贡献。
那么我们要判断有多少连续区间的点跨过这条边,并不好算,反过来去求在这条边的两侧分别有多少个连续区间。
那么显然有\(O(n^2)\)的做法,即对每条边DFS它的两侧,枚举一下每一侧的连续区间。
我们还可以DFS这棵树,对于每个点我们需要计算它子树内和子树外的连续区间数。
对于子树内的点,并查集显然是可以维护的(每次合并相邻点成为一个连续区间时新产生的连续区间数可算,就是两个集合\(size\)的乘积)。
对于子树外的点怎么算呢。我们可以先假设子树外为\(1,2,...,n\),且有这些区间。我们每次在子树中加入点时,就把它从子树外的集合的点中删掉,并计算子树外少的区间数。
我们发现不需要\(n\)个元素的集合,也不需要这样删。在一个初始只有\(0\)和\(n+1\)的空集合里,每次加入子树内的点,然后计算,也是等价的。
于是我们需要对每个子树合并并查集及处理set。
可以用dsu on tree,每次先处理完轻儿子的子树,每次处理完都清空那棵子树的贡献/状态(并查集、set)。最后处理重儿子所在子树,并保留其状态。
处理完这整棵子树后(也就是算完该边答案后),再把子树内其它未加入的轻子树给加入并查集、set。
dsu的复杂度是\(O(n\log n)\)的,再套上别的,复杂度为\(O(n\log^2n)\)。

#include <set>
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 150000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define Calc(x) (1ll*(x)*(x-1)>>1ll)//区间个数
typedef long long LL;
const int N=1e5+5;

int n,Enum,H[N],nxt[N<<1],to[N<<1],fa[N],sz[N],son[N],Fa[N],size[N];
bool vis[N];//计算子树内的连续区间(是否子树内已存在相邻的)
LL Ans,sum1,sum2;
std::set<int> st;
char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline void AE(int u,int v)
{
    to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
    to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum;
}
void DFS1(int x)
{
    int mx=0; sz[x]=1;
    for(int i=H[x],v; i; i=nxt[i])
        if((v=to[i])!=fa[x])
        {
            fa[v]=x, DFS1(v), sz[x]+=sz[v];
            if(sz[v]>mx) mx=sz[v], son[x]=v;
        }
}
int Find(int x)
{
    return x==Fa[x]?x:Fa[x]=Find(Fa[x]);
}
void Upd(int x)
{
    st.insert(x);//我怎么记得有返回值(iterator)来...
    std::set<int>::iterator it=st.find(x),pre=it,nxt=++it;
    --pre;
    sum2-=Calc(*nxt-*pre-1);//子树外以前的连续区间被分割
    sum2+=Calc(x-*pre-1)+Calc(*nxt-x-1);

    vis[x]=1;
    if(vis[x-1])
    {
        int r1=Find(x-1),r2=Find(x);//它们之前显然不会在一个集合啊(它们之间只会算一次,要么是加x-1,以前有x;要么是加x,以前有x-1)。
        sum1+=1ll*size[r1]*size[r2];
        Fa[r1]=r2, size[r2]+=size[r1];
    }
    if(vis[x+1])
    {
        int r1=Find(x+1),r2=Find(x);
        sum1+=1ll*size[r1]*size[r2];
        Fa[r1]=r2, size[r2]+=size[r1];
    }
}
void Clear(int x)
{
    Fa[x]=x, size[x]=1, vis[x]=0;
    for(int i=H[x]; i; i=nxt[i])
        if(to[i]!=fa[x]) Clear(to[i]);
}
void Update(int x)
{
    Upd(x);
    for(int i=H[x]; i; i=nxt[i])
        if(to[i]!=fa[x]) Update(to[i]);
}
void DFS2(int x)
{
    for(int i=H[x],v; i; i=nxt[i])
        if((v=to[i])!=fa[x]&&v!=son[x])
        {
            DFS2(v), Clear(v);
            st.clear(), st.insert(0), st.insert(n+1);
            sum1=0, sum2=Calc(n);//还是在DFS完子树后就初始化吧
        }
    if(son[x]) DFS2(son[x]);

    for(int i=H[x],v; i; i=nxt[i])
        if((v=to[i])!=fa[x]&&v!=son[x]) Update(v);

    Upd(x);
    Ans+=Calc(n)-sum1-sum2;
}

int main()
{
    freopen("treecnt.in","r",stdin);
    freopen("treecnt.out","w",stdout);

    n=read();
    for(int i=1; i<n; ++i) AE(read(),read());
    for(int i=1; i<=n; ++i) Fa[i]=i,size[i]=1;//!
    st.insert(0), st.insert(n+1);//边界.
    sum1=0, sum2=Calc(n), DFS1(1), DFS2(1), printf("%lld\n",Ans);

    return 0;
}

另一种\(O(n^2)\)做法:

#include <set>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=1e5+5,INF=0x3f3f3f3f;

int dgr[N],Enum,H[N],nxt[N<<1],to[N<<1],fa[N],dep[N],sz[N],son[N],top[N],dfn[N],ref[N];
char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline void AE(int u,int v)
{
//  ++dgr[u], ++dgr[v];
    to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
    to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum;
}
inline int LCA(int u,int v)
{
//  printf("LCA(%d,%d)\n",u,v);
    while(top[u]!=top[v]) dep[top[u]]>dep[top[v]]?u=fa[top[u]]:v=fa[top[v]];
    return dep[u]>dep[v]?v:u;
}
inline int Dis(int u,int v)
{
//  printf("(%d,%d) Subd:dep[%d]=%d!\n",u,v,LCA(u,v),dep[LCA(u,v)]);
    return dep[u]+dep[v]-(dep[LCA(u,v)]<<1);
}
void DFS1(int x)
{
    int mx=0; sz[x]=1;
    for(int i=H[x],v; i; i=nxt[i])
        if((v=to[i])!=fa[x])
        {
            fa[v]=x, dep[v]=dep[x]+1, DFS1(v), sz[x]+=sz[v];
            if(sz[v]>mx) mx=sz[v], son[x]=v;
        }
}
void DFS2(int x,int tp)
{
    static int Index=0;

    top[x]=tp, dfn[x]=++Index;
    if(son[x])
    {
        DFS2(son[x],tp);
        for(int i=H[x]; i; i=nxt[i])
            if(to[i]!=fa[x]&&to[i]!=son[x]) DFS2(to[i],to[i]);
    }
}
inline bool cmp_dfn(int i,int j)
{
    return dfn[i]<dfn[j];
}
void Output(int *l,int *r,int n)
{
    printf("\nOutput the list:\n%d",0);
    for(int p=r[0]; p!=n+1; p=r[p]) printf("->%d(%d)",p,l[p]); puts("\n");
}
void Subtask0(int n)
{
    const int N=305;
    static int A[N];

    LL ans=0;
    for(int i=1; i<=n; ++i)
    {
        int t=1; A[1]=i;
        for(int j=i+1; j<=n; ++j)
        {
            A[++t]=j, std::sort(A+1,A+1+t,cmp_dfn);
            for(int k=1; k<t; ++k) ans+=Dis(A[k],A[k+1]);
            ans+=Dis(A[t],A[1]);
        }
    }
    printf("%I64d\n",ans>>1ll);
}
void Subtask1(int n)
{
    const int N=3005;
    static int A[N],SL[N],SR[N],L[N],R[N],id[N],tl[N],tr[N];

    LL ans=0;
    for(int i=1; i<=n; ++i) ans+=1ll*(1ll*(i-1)*(n-i+1)+n-i)*dep[i],id[i]=i;
//  printf("pre_ans=%I64d\n\n",ans);

    std::sort(id+1,id+1+n,cmp_dfn);
    SR[0]=id[1], SL[n+1]=id[n], id[n+1]=n+1;
    for(int i=1; i<=n; ++i) SL[id[i]]=id[i-1], SR[id[i]]=id[i+1];

//  for(int i=1; i<=n; ++i) printf("dfn[%d]=%d\n",i,dfn[i]); puts("");
//  Output(SL,SR,n);

    for(int i=1; i<n; ++i)
    {
        memset(tl,0,sizeof tl), memset(tr,0,sizeof tr);
        memcpy(L,SL,sizeof SL), memcpy(R,SR,sizeof SR);
//      Output(L,R,n);

        for(int j=n,T=1,l,r; j>i; --j,++T)
        {
            int a,b;
            if((l=L[j])==0) a=j, b=L[n+1];
            else a=j, b=l;
            int tmp=std::min(T-tr[b],T-tl[a]);
            ans-=1ll*tmp*dep[LCA(a,b)], tr[b]=T;

            if((r=R[j])==n+1) b=j, a=R[0];
            else b=j, a=r;
            tmp=std::min(T-tr[b],T-tl[a]);
            ans-=1ll*tmp*dep[LCA(a,b)], tl[a]=T;

            R[l]=r, L[r]=l;

//          if((l=L[j])==0) ans-=dep[LCA(j,L[n+1])], printf("Subd:dep[%d]=%d!\n",LCA(j,L[n+1]),dep[LCA(j,L[n+1])]);
//          else ans-=dep[LCA(j,l)], printf("Subd:dep[%d]=%d!\n",LCA(j,l),dep[LCA(j,l)]);
//          if((r=R[j])==n+1) ans-=dep[LCA(j,R[0])], printf("Subd:dep[%d]=%d!\n",LCA(j,R[0]),dep[LCA(j,R[0])]);
//          else ans-=dep[LCA(j,r)], printf("Subd:dep[%d]=%d!\n",LCA(j,r),dep[LCA(j,r)]);
//          R[l]=r, L[r]=l;
        }
        SR[SL[i]]=SR[i], SL[SR[i]]=SL[i];
    }
    printf("%I64d\n",ans);
}
namespace Subtask2
{
//  struct BIT
//  {
//      #define lb(x) (x&-x)
//      int n,mn[N],mx[N];
//
//      void Init(int nn) {n=nn; memset(mn,0x3f,sizeof mn), memset(mx,0,sizeof mx);}
//      void Modify_Max(int p,int v)
//      {
//          for(; p<=n; p+=lb(p)) mx[p]=std::max(mx[p],v);
//      }
//      void Modify_Min(int p,int v)
//      {
//          for(; p<=n; p+=lb(p)) mn[p]=std::min(mn[p],v);
//      }
//      int Query_Max(int p)
//      {
//          int res=0;
//          for(; p; p^=lb(p)) res=std::max(res,mx[p]);
//          return res;
//      }
//      int Query_Min(int p)
//      {
//          int res=INF;
//          for(; p; p^=lb(p)) res=std::min(res,mn[p]);
//          return res;
//      }
//  }Tp,Ts;
//  int pos[N],ref[N];
//
//  void DFS3(int x,int f,int t)
//  {
////        pos[x]=t, ref[t]=x;
//      pos[t]=x;
//      for(int i=H[x]; i; i=nxt[i])
//          if(to[i]!=f) DFS3(to[i],x,t+1);
//  }
//  bool Main(int n)
//  {
//      for(int i=1; i<=n; ++i) if(dgr[i]>2) return 0;
//      for(int i=1; i<=n; ++i) if(dgr[i]==1) {DFS3(i,i,1); break;}
//
//      LL ans=0;
//      Tp.Init(n), Ts.Init(n);
//      for(int i=n; i; --i)
//      {
//          int p=pos[i],l=Tp.Query_Max(p)+1,r=Ts.Query_Min(n-p+1)-1;
////            if(l==0+1) l=1;
//          if(r==INF-1) r=n;
//          ans+=1ll*i*(p-l+1)*(r-p+1);
//          Tp.Modify_Max(p,p), Ts.Modify_Min(n-p+1,p);
//      }
//
//      Tp.Init(n), Ts.Init(n);
//      for(int i=1; i<=n; ++i)
//      {
//          int p=pos[i],l=Tp.Query_Max(p)+1,r=Ts.Query_Min(n-p+1)-1;
////            if(l==0+1) l=1;
//          if(r==INF-1) r=n;
//          ans-=1ll*i*(p-l+1)*(r-p+1);
//          Tp.Modify_Max(p,p), Ts.Modify_Min(n-p+1,p);
//      }
//      printf("%I64d\n",ans);
//      return 1;
//  }
}

int main()
{
    freopen("treecnt.in","r",stdin);
    freopen("treecnt.out","w",stdout);

    int n=read();
    for(int i=1; i<n; ++i) AE(read(),read());

//  if(n>3000 && Subtask2::Main(n)) return 0;

    DFS1(1), DFS2(1,1);
//  Subtask0(n); puts("");
    if(n<=300) Subtask0(n);
    else if(n<=3000||1) Subtask1(n);

    return 0;
}

原文地址:https://www.cnblogs.com/SovietPower/p/9688048.html

时间: 2024-11-06 03:33:05

树上统计treecnt(dsu on tree 并查集 正难则反)的相关文章

CF109 C. Lucky Tree 并查集

Petya loves lucky numbers. We all know that lucky numbers are the positive integers whose decimal representations contain only the lucky digits 4 and 7. For example, numbers 47, 744, 4 are lucky and 5, 17, 467 are not. One day Petya encountered a tre

HDU 1325 Is It A Tree? 并查集

判断是否为树 森林不是树 空树也是树 成环不是树 数据: 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 1 0 0 1 2 2 3 4 5 0 0 2 5 0 0 ans: no no yes #include <stdio.h> #include <string.h> #include <stdlib.h> #include <limits.h> #include <malloc.h> #include <ctype

HDU1325 Is It A Tree? 并查集

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1325 这题与HDU1272 小希的迷宫 (并查集) 非常像,不过细细看,还是有一点区别的.就是这题的路径是单向的,每次只能由起点指向终点,在连接之前终点必须是根节点. 注意的问题: 1.不能成环,即每次输入的两个数的根节点不能相同: 2.最终根节点数目为一 3.注意当只输入"0 0" 时要输出"Case %d is a tree." 4.路径是单向的,即每次只能由起点指

图论-树上启发式合并(DSU On Tree)

Disjoint Set Union On Tree ,似乎是来自 Codeforces 的一种新操作,似乎被叫做"树上启发式合并". 在不带修改的有根树子树信息统计问题中,似乎树上莫队和这个 DSU On Tree 是两类常规操作. 先对树按轻重链剖分.对于每个节点,先计算轻儿子为根的子树信息,每次计算后消除影响,再去计算其他轻儿子.然后计算重儿子为根的子树信息,不消除影响,并把轻儿子们为根的子树信息加入,再合并这个节点本身的信息.由于一个大小 \(x\) 的子树被消除影响后,都把信

Is It A Tree?(并查集)(dfs也可以解决)

Is It A Tree? Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Submit Status Description A tree is a well-known data structure that is either empty (null, void, nothing) or is a set of one or more nodes connected by direct

HDU 5606 tree 并查集

tree 把每条边权是1的边断开,发现每个点离他最近的点个数就是他所在的连通块大小. 开一个并查集,每次读到边权是0的边就合并.最后Ans?i??=size[findset(i)],size表示每个并查集根的size Ans_i=size[findset(i)],sizeAns?i??=size[findset(i)],size表示每个并查集根的sizesize. #include<cstdio> #include<cstring> #include<algorithm>

HDU 1325 POJ 1308 Is It A Tree? (并查集)

这道题就是裸并查集,关键在于对不是树几种的判断 1. 空树是树 2. 森林不是树 3. 无环 或者从入度来看:1,无环:2,除了根,所有的入度为1,根入度为0:3,这个结构只有一个根,不然是森林了. 这道题本来暑假做的POJ 1308 但是HDU没有过.在于空树没有考虑. 用并查集判断有多少个森林注意编号是随机的,不是次序.... /* input: 0 0 1 1 0 0 1 2 1 2 0 0 1 2 2 3 4 5 0 0 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

hdu5606 tree (并查集)

tree Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 823    Accepted Submission(s): 394 Problem Description There is a tree(the tree is a connected graph which contains n points and n−1 edges),t

swust oj 856--Huge Tree(并查集)

题目链接:http://acm.swust.edu.cn/problem/856/ Time limit(ms): 1000 Memory limit(kb): 10000 There are N trees in a forest. At first, each tree contains only one node as its root. And each node is marked with a number. You're asked to do the following two