逛森林

这是一道模板题

首先,对任意时刻,\(u\)->\(v\)这条路径上的点都是不会变动的(就是说,比如,如果某时刻从\(1\)\(4\)的路径为\(1\)->\(3\)->\(4\),那么对之后的任意时刻,这条路径都是这个,既不会改变顺序,也不会新增节点,更不会删除已有节点),所以我们可以把所有有效的操作一存起来最后再建边

那么这里利用倍增优化建边

倍增的分治结构是什么?

这个分治的状态是不是就出来了?注意绿边的边权为0

比如说我现在有一个传送门,是从\(x_0\)->\(x_1\)这条链到\(x_2\)->\(x_4\)这条链,那么我就会从\(f[x_0][0]\)的out点连一条边权为\(w\)的出边到虚拟节点\(T\),再从\(T\)连一条边权为0的入边到\(f[x_2][1]\)的in点,由于绿边的边权为0,显然就等价于从\(x_0\)->\(x_1\)这条链上的每一个点都连了一条边权为\(w\)的出边到\(x_2\)->\(x_4\)这条链上的每一个点

那么这个的复杂度是多少?首先考虑树上的边,总共是\(O(m)\)条,然后考虑绿边,因为每个树上的点顶多向上跳\(logn\)次,所以in点和out点的个数就是\(O(nlogn)\),由前文描述可得每个in点和out点至多会连三条有向边,所以绿边的个数就是\(O(nlogn)\),然后在考虑虚拟节点的边,对每一次操作,我们最多跳\(logn\)步,每跳一步就会连边,所以一共是\(O(mlogn)\)的条边,最后跑dij,总共的时间复杂度为\(O((nlogn+mlogn)logn)\)

考虑优化,利用st表的思想,对一条长度为\(2^k+p\)的链,我们将其分成两步,前\(2^k\)和后\(2^k\)个节点的in点和out点分别按照上述方法连边,尽管有重复连边,但是不影响答案,时间复杂度被优化到了\(O((nlogn+m)logn)\),如下图

这两个区间代表的节点进行连边就好了

其他的优化建边

update 2024.5.12

我们来说一下代码的细节

首先,千万不要认为我们对每个点都会建一颗有\(O(nlogn)\)个节点的树,从而导致节点总数是\(O(n^2logn)\),因为我们dfs到某一个点的时候,这条链上的其余点的树都已经建好了,不用再重新建,听不懂的话就看代码

然后,在求连边的节点时,不要用st表的写法(预处理出Log数组然后求出最大长度),因为这不是链,而是树;我们应该直接对代码中LCA的部分进行改动(然而这样的话时间复杂度就没办法优化了)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e4+10,M=1e6+10,INF=0x3f3f3f3f;
int n,m,s,cnt,tot;
int fa[N];
int End[M<<5],Len[M<<5],Last[M<<2],Next[M<<5],dis[M<<2];
int dep[N],f[N][20],in[N][20],out[N][20];
bool vis[M<<2],mark[M];
int u[M][3],v[M][3],w[M],op[M];
vector<int> G[N];
struct Node
{
	int Num,dis;
	bool operator<(const Node &a) const
	{
		return a.dis<dis;
	}
};
priority_queue<Node> q;
int getfa(int x)
{
	return fa[x]==x?x:fa[x]=getfa(fa[x]);
}
void merge(int x,int y)
{
	fa[getfa(x)]=getfa(y);
}
void add(int x,int y,int z) 
{
	End[++tot]=y,Len[tot]=z,Next[tot]=Last[x],Last[x]=tot;
}
void init(int x,int fa)
{
	dep[x]=dep[fa]+1;
	for(int i=0;i<=17;i++)
    {
    	f[x][i+1]=f[f[x][i]][i];
    	in[x][i+1]=++cnt,add(cnt,in[x][i],0),add(cnt,in[f[x][i]][i],0);
		out[x][i+1]=++cnt,add(out[x][i],cnt,0),add(out[f[x][i]][i],cnt,0);
		//in表示出点,out表示入点 
	}
    for(int i=1;i<=G[x].size();i++)
    {
    	int v=G[x][i-1];
        if(v!=fa)
        {
            f[v][0]=x;
            in[v][0]=++cnt,add(cnt,v,0),add(cnt,x,0);
			out[v][0]=++cnt,add(v,cnt,0),add(x,cnt,0);
            init(v,x);
        }
    }
}
void LCA(int ver,int id)//直接对LCA的代码进行改动连边 
{
	int x=u[ver][id],y=v[ver][id];
    if(x==y) 
    {
    	if(id==1) add(x,cnt,0);
    	else add(cnt,x,0);
    	return;
	} 
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=17;i>=0;i--)
    {
        if(dep[f[x][i]]>=dep[y])
        {
        	if(id==1) add(out[x][i],cnt,0);
        	else add(cnt,in[x][i],0);
        	x=f[x][i];
		}
        if(x==y) return;
        if(dep[x]==dep[y]) break;
    }
    for(int i=17;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
        	if(id==1) add(out[x][i],cnt,0),add(out[y][i],cnt,0);
        	else add(cnt,in[x][i],0),add(cnt,in[y][i],0);
            x=f[x][i];
            y=f[y][i];
        }
    }
    if(id==1) add(out[x][0],cnt,0),add(out[y][0],cnt,0);
    else add(cnt,in[x][0],0),add(cnt,in[y][0],0);
    return;
}
void Dij()
{
    Node temp;
    temp.Num=s;
    temp.dis=0;
    dis[s]=0;
    q.push(temp);
    while(!q.empty())
    {
        int u=q.top().Num;
        q.pop();
        if(vis[u]==1) continue;
        vis[u]=1;
        for(int i=Last[u];i;i=Next[i])
        {
            int v=End[i];
            if(dis[v]>dis[u]+Len[i])
            {
                dis[v]=dis[u]+Len[i];
                temp.Num=v;
                temp.dis=dis[v];
                q.push(temp);
            }
        }
    }
}
int main()
{
	scanf("%d%d%d",&n,&m,&s);
	memset(dis,0x3f,sizeof(dis));
	cnt=n;//cnt表示节点总数 
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&op[i],&u[i][1],&v[i][1]);
		if(op[i]==1) 
		{
			scanf("%d%d%d",&u[i][2],&v[i][2],&w[i]);
			mark[i]=1;//mark表示这次操作是否有效 
			for(int j=1;j<=2;j++)
			if(getfa(u[i][j])!=getfa(v[i][j]))
			{
				mark[i]=0;
				break;
			}
		}
		else 
		{
			scanf("%d",&w[i]);
			if(getfa(u[i][1])!=getfa(v[i][1]))
			{
				mark[i]=1;
				merge(u[i][1],v[i][1]);
				G[u[i][1]].push_back(v[i][1]); 
				G[v[i][1]].push_back(u[i][1]);
				//注意树中的边一定要和最终图的边分开存
				//如果存一起的话
				//在init过程中,由于我们要加边
				//就会把边加进去,破坏树的结构 
			}
		}
	}
	for(int i=1;i<=n;i++)
	if(!dep[i]) init(i,0);
	//注意森林的每一颗树都要init
	//因为加了传送门之后可能连通 
	for(int i=1;i<=m;i++)
	if(op[i]==1&&mark[i])
	{
		for(int j=1;j<=2;j++)
		{
			cnt++;//创建虚拟节点 
			LCA(i,j);
		}
		add(cnt-1,cnt,w[i]);//两个虚拟节点连边 
	}
	else if(op[i]==2&&mark[i])
	add(u[i][1],v[i][1],w[i]),add(v[i][1],u[i][1],w[i]);//注意树中的边也要添加到最终图中 
	Dij();
	for(int i=1;i<=n;i++)
	printf("%d ",dis[i]>INF/2?-1:dis[i]);
	return 0;
} 
posted @ 2023-12-14 15:48  最爱丁珰  阅读(2)  评论(0编辑  收藏  举报