2023.4.21图论点分治

Posted fanghaoyu801212

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023.4.21图论点分治相关的知识,希望对你有一定的参考价值。

2023.4.21【图论】点分治

(点分治其实是广泛地统计树上全局路径问题的算法,此处采用luogu模板题的题面)

题目描述

给定一棵有 \\(n\\) 个点的树,询问树上距离为 \\(k\\) 的点对是否存在。

  • 对于 \\(100\\%\\) 的数据,保证 \\(1 \\leq n\\leq 10^4\\)\\(1 \\leq m\\leq 100\\)\\(1 \\leq k \\leq 10^7\\)\\(1 \\leq u, v \\leq n\\)\\(1 \\leq w \\leq 10^4\\)

算法描述

点分治,又名淀粉质,是计算树上路径问题的算法,与树剖不同的是,它计算的是对于整棵树的所有路径的情况,树剖难以完成。此题有\\(m \\leq 100\\)个询问,我们考虑每一条路径,发现当我们枚举一个点\\(x\\)时,一条路径要么是经过\\(x\\)的,要么是与\\(x\\)不相交的。对于那些与x不相交的路径,我们可以向下分治来讨论。

我们发现,对于过x,但是同时经过x的父亲的路径,在\\(x\\)的父亲一层会考虑,所以我们只讨论\\(x\\)的子树以内的路径,遍历每一棵\\(x\\)的子树(直接连接的儿子),计算子树中每一个数到\\(x\\)的距离\\(dis\\),将它们推进一个桶\\(judge\\)中,为了方便处理,我们将这个子树的点的\\(dis\\)在遍历时推进一个队列,我们扫描队列中的每个元素,如果\\(judge[k_i - dis]\\)是存在的,那么说明存在一条长为\\(k\\)的路径,从\\(x\\)先前的子树向上到\\(x\\),再向下转到当前子树中的点,这个询问就有答案,鉴于询问数\\(m \\leq 100\\),直接枚举每个\\(k_i\\),然后用\\(O(1)\\)判断每个\\(k_i\\)是否可以成立即可(这个地方开桶有一种空间换时间的思想)。对于每次清空\\(judge\\)数组,不能直接memset,要记录你更改了哪些值,再改回去就好了。

这是计算(calc)函数:

inline void calc(int x)

    top = 0;
    for(int i = head[x];i;i = e[i].next)
    
        int to = e[i].v;
        if(vis[to]) continue;
        dis[to] = e[i].w;
        rem[0] = 0;//rem[0]是计数器,rem是队列
        getdis(to,x);
        for(int j = 1;j <= rem[0];j++)
            for(int k = 1;k <= m;k++)
                if(query[k] - rem[j] >= 0)
                    if(judge[query[k] - rem[j]] == 1)
                        rt[k] = 1;
        for(int j = 1;j <= rem[0];j++)
            if(rem[j] <= T)
                q[++top] = rem[j],judge[rem[j]] = 1;
    
    for(int i = 1;i <= top;i++) judge[q[i]] = 0;

时间复杂度\\(O(nm\\ logn)\\)

讲解视频:https://www.bilibili.com/video/BV1GJ411x7h7

上条链不久被卡死了qwq

这里需要打破传统的树形问题的遍历顺序,而是每次处理一棵子树时,以这棵子树的重心为根进行处理,这样就能保证把子树切开后,大小一定会小于原来的\\(\\frac 12\\),所以就能保证复杂度严格,但是遍历顺序改变了,所以遍历过的点要将一个\\(vis\\)标签打为1,在所有函数的遍历中,如果\\(vis_son = 1\\),就不访问\\(son\\)

找重心部分:(一个点为根子树大小的最大值最小)

inline void dfs(int x,int last)

	siz[x] = 1;
	int now = 0;
	for(int i = head[x];i;i = e[i].next)
	
		int to = e[i].v;
		if(to == last || vis[to]) continue;
		dfs(to,x);
		siz[x] += siz[to];
		now = max(now,siz[to]);
	
	now = max(now,sum - siz[x]);
	if(now < Minsiz) Minsiz = now,root = x;

注意到一个细节:函数里面的“整棵树大小”用了一个\\(sum\\)变量,是因为每一次在子树中递归时总大小不一样,因为这次是确定了以x为根,下一次进入到儿子\\(son\\)中,就可以直接用\\(siz_son\\)作为\\(sum\\)。由于以\\(son\\)为根dfs时只会改变\\(son\\)子树的\\(siz\\),所以对于\\(x\\)的其他子树没有影响。

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5,T = 1e7 + 5;
struct Edge
	int v,w,next;
e[N * 5];
int head[N],n,m,vis[N],dis[N],query[N],judge[N * 100],rt[N],siz[N],rem[N],root = 0,Minsiz = 0x3f3f3f3f,tot = 0,sum,q[N],top = 0;
inline void add(int x,int y,int z)

	++tot;
	e[tot].v = y;
	e[tot].w = z;
	e[tot].next = head[x];
	head[x] = tot;

inline void dfs(int x,int last)

	siz[x] = 1;
	int now = 0;
	for(int i = head[x];i;i = e[i].next)
	
		int to = e[i].v;
		if(to == last || vis[to]) continue;
		dfs(to,x);
		siz[x] += siz[to];
		now = max(now,siz[to]);
	
	now = max(now,sum - siz[x]);
	if(now < Minsiz) Minsiz = now,root = x;

inline void getdis(int x,int last)

	rem[++rem[0]] = dis[x];
	for(int i = head[x];i;i = e[i].next)
	
		int to = e[i].v;
		if(to == last || vis[to]) continue;
		dis[to] = dis[x] + e[i].w;
		getdis(to,x);
	

inline void calc(int x)

	top = 0;
	for(int i = head[x];i;i = e[i].next)
	
		int to = e[i].v;
		if(vis[to]) continue;
		dis[to] = e[i].w;
		rem[0] = 0;
		getdis(to,x);
		for(int j = 1;j <= rem[0];j++)
			for(int k = 1;k <= m;k++)
				if(query[k] - rem[j] >= 0)
					if(judge[query[k] - rem[j]] == 1)
						rt[k] = 1;
		for(int j = 1;j <= rem[0];j++)
			if(rem[j] <= T)
				q[++top] = rem[j],judge[rem[j]] = 1;
	
	for(int i = 1;i <= top;i++) judge[q[i]] = 0;

inline void solve(int x)

	judge[0] = 1;
	vis[x] = 1;
	calc(x);
	for(int i = head[x];i;i = e[i].next)
	
		int to = e[i].v;
		if(vis[to]) continue;
		sum = siz[to];
		Minsiz = 0x3f3f3f3f;
		dfs(to,0);
		solve(root);
	

int main()

	memset(siz,0,sizeof(siz));
	int x,y,z;
	cin>>n>>m;
	for(int i = 1;i <= n - 1;i++)
	
		cin>>x>>y>>z;
		add(x,y,z);
		add(y,x,z);
	
	for(int i = 1;i <= m;i++) cin>>query[i];
	sum = n;
	dfs(1,0);
	memset(judge,0,sizeof(judge));
	memset(rt,0,sizeof(rt));
	memset(vis,0,sizeof(vis));
	solve(root);
	for(int i = 1;i <= m;i++)
		if(rt[i])
			cout<<"AYE"<<endl;
		else
			cout<<"NAY"<<endl;
	return 0;

bzoj4025 二分图 [分治,并查集]

传送门


思路

是二分图的充要条件:图没有奇环。

考虑按时间分治,用可撤销并查集维护点到根的距离。

仍然可以用一个小trick把两点连边变成根连边,可以看这里

每次连边时若不连通则连上,否则判一下有没有奇环。如果有输出“No”,否则不用连。

我tm把T写成m狂WA不止


#include<bits/stdc++.h>
namespace my_std{
    using namespace std;
    #define pii pair<int,int>
    #define fir first
    #define sec second
    #define MP make_pair
    #define rep(i,x,y) for (int i=(x);i<=(y);i++)
    #define drep(i,x,y) for (int i=(x);i>=(y);i--)
    #define go(x) for (int i=head[x];i;i=edge[i].nxt)
    #define sz 202020
    typedef long long ll;
    template<typename T>
    inline void read(T& t)
    {
        t=0;char f=0,ch=getchar();
        double d=0.1;
        while(ch>‘9‘||ch<‘0‘) f|=(ch==‘-‘),ch=getchar();
        while(ch<=‘9‘&&ch>=‘0‘) t=t*10+ch-48,ch=getchar();
        if(ch==‘.‘)
        {
            ch=getchar();
            while(ch<=‘9‘&&ch>=‘0‘) t+=d*(ch^48),d*=0.1,ch=getchar();
        }
        t=(f?-t:t);
    }
    template<typename T,typename... Args>
    inline void read(T& t,Args&... args){read(t); read(args...);}
    void file()
    {
        #ifndef ONLINE_JUDGE
        freopen("a.txt","r",stdin);
        #endif
    }
//  inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std;

int n,m,T;
struct hh{int f,t;hh(int ff=0,int tt=0){f=ff,t=tt;}}edge[sz];

int fa[sz],dep[sz],f[sz];
int getfa(int x){return x==fa[x]?x:getfa(fa[x]);}
int getdis(int x){return x==fa[x]?0:f[x]^getdis(fa[x]);}
vector<hh>v[sz<<2];
#define ls k<<1
#define rs k<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
void insert(int k,int l,int r,int x,int y,hh e)
{
    if (x>y) return;
    if (x<=l&&r<=y) return (void)v[k].push_back(e);
    int mid=(l+r)>>1;
    if (x<=mid) insert(lson,x,y,e);
    if (y>mid) insert(rson,x,y,e);
}
struct hhh{int x,y;bool s;};
void resume(stack<hhh>S){while (!S.empty()) f[fa[S.top().x]=S.top().x]=0,dep[S.top().y]-=S.top().s,S.pop();}
void solve(int k,int l,int r)
{
    stack<hhh>S;
    rep(i,0,(int)v[k].size()-1)
    {
        hh e=v[k][i];int x=e.f,y=e.t;
        int fx=getfa(x),fy=getfa(y);
        int w=getdis(x)^getdis(y)^1;
        if (fx==fy&&w) { rep(i,l,r) puts("No"); resume(S); return; }
        if (fx!=fy)
        {
            if (dep[fx]>dep[fy]) swap(fx,fy),swap(x,y);
            hhh cur=(hhh){fx,fy,0};
            fa[fx]=fy;f[fx]=w;
            if (dep[fx]==dep[fy]) ++dep[fy],cur.s=1;
            S.push(cur);
        }
    }
    if (l==r) return puts("Yes"),resume(S);
    int mid=(l+r)>>1;
    solve(lson);solve(rson);
    resume(S);
}

int main()
{
    file();
    read(n,m,T);
    rep(i,1,n) fa[i]=i;
    int x,y,l,r;
    rep(i,1,m) read(x,y,l,r),edge[i]=hh(x,y),++l,insert(1,1,n,l,r,edge[i]);
    solve(1,1,T); // orz
    return 0;
}

以上是关于2023.4.21图论点分治的主要内容,如果未能解决你的问题,请参考以下文章

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

bzoj4025 二分图 [分治,并查集]

线段树分治

[hdu 5354] Bipartite Graph 分治 并查集

3237: [Ahoi2013]连通图 线段树分治

[BZOJ4025]二分图(线段树分治,并查集)