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


    题目链接

    dalao们怎么都写的线段树合并啊。。
    dsu跑的好慢。


    (Description)

    给定一棵(n(nleq 10^5))个点的树。
    定义(Tree[L,R])表示为了使得(Lsim R)号点两两连通,最少需要选择的边的数量。
    求$$sum_{l=1}^nsum_{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(nlog n))的,再套上别的,复杂度为(O(nlog^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
    ",Ans);
    
    	return 0;
    }
    

    某一种(O(n^2))做法:(见Subtask1()
    写了个双链表。。心疼zz的自己。。

    #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)
    ",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!
    ",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("
    Output the list:
    %d",0);
    	for(int p=r[0]; p!=n+1; p=r[p]) printf("->%d(%d)",p,l[p]); puts("
    ");
    }
    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
    ",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
    
    ",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
    ",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!
    ",LCA(j,L[n+1]),dep[LCA(j,L[n+1])]);
    //			else ans-=dep[LCA(j,l)], printf("Subd:dep[%d]=%d!
    ",LCA(j,l),dep[LCA(j,l)]);
    //			if((r=R[j])==n+1) ans-=dep[LCA(j,R[0])], printf("Subd:dep[%d]=%d!
    ",LCA(j,R[0]),dep[LCA(j,R[0])]);
    //			else ans-=dep[LCA(j,r)], printf("Subd:dep[%d]=%d!
    ",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
    ",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
    ",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;
    }
    
  • 相关阅读:
    jvm.option是什么,它是如何加载的
    适配器模式--想象一下转换插头
    Android gradle 配置
    Android全面屏适配
    github控件地址
    TextView 链接显示及跳转
    解决java.lang.annotation.AnnotationFormatError: Invalid default: public abstract java.lang.Class org.robolectric.annotation.Config.application()
    解决Android中,禁止ScrollView内的控件改变之后自动滚动
    Android跳转到应用商店的APP详情页面,以及 Google GMS 各个apk的包
    EditText小记
  • 原文地址:https://www.cnblogs.com/SovietPower/p/9688048.html
Copyright © 2020-2023  润新知