常见优化建图技巧

一、线段树优化建图

基本操作

  1. x 向区间 [l,r] 连边
  2. 区间 [l,r]x 连边
  3. 区间 [l,r] 向 区间 [x,y] 连边

建立两棵线段树,一棵从父亲节点向儿子节点连长度为 0 的边,称为出树;一棵从儿子节点向父亲连长度为 0 的边,称为入树。并且在出树和入树的相同叶子节点间连长度为 0 的边

对于操作1,在入树中找到 x 代表的点,向出树中 [l,r] 覆盖的点连边

对于操作2,在入树中找到 [l,r] 覆盖的点,向出树中 x 代表的的点连边

对于操作3,在入树中找到 [l,r] 覆盖的点,向虚点连边,虚点再向出树中 [x,y] 覆盖的点连边

这样,我们将边数规模从 O(n2m) 降到了 O(mlogn),方便我们进行后续操作

CF786B Legacy & P6348 [PA2011] Journeys

板子题

code
// CF786B
#include<bits/stdc++.h>
#define pii pair<int,int>
#define pli pair<long long,int>
#define LL long long
using namespace std;

const int N=100010;

int n,m,s;
vector <pii> g[9*N];

void add(int x,int y,int z)
{
	g[x].push_back({y,z});
}

namespace TR
{
	#define lc(p) p*2
	#define rc(p) p*2+1
	int cnt;
	
	struct SegmentTree
	{
		int id[4*N],pos[N];
		
		void build(int p,int l,int r,int flag)
		{
			id[p]=++cnt;
			if(l==r)
			{
				pos[l]=cnt;
				return;
			}
			int mid=(l+r)>>1;
			build(lc(p),l,mid,flag);  build(rc(p),mid+1,r,flag);
			if(!flag)
				add(id[p],id[lc(p)],0),add(id[p],id[rc(p)],0);
			else
				add(id[lc(p)],id[p],0),add(id[rc(p)],id[p],0);
		}
		
		void addt(int p,int l,int r,int ql,int qr,int u,int w,int flag)
		{
			if(ql<=l && qr>=r)
			{
				if(!flag)
					add(u,id[p],w);
				else
					add(id[p],u,w);
				return;
			}
			int mid=(l+r)>>1;
			if(ql<=mid)
				addt(lc(p),l,mid,ql,qr,u,w,flag);
			if(qr>mid)
				addt(rc(p),mid+1,r,ql,qr,u,w,flag);
		}
	}t[2];
}

namespace GR
{
	LL d[9*N];  bool v[9*N];
	priority_queue <pli> q;
	
	void dijkstra()
	{
		memset(d,0x3f,sizeof(d));
		d[TR::t[1].pos[s]]=0;  q.push({0,TR::t[1].pos[s]}); 
		while(q.size())
		{
			int x=q.top().second;  q.pop();
			if(v[x])
				continue;
			v[x]=1;
			for(auto vv:g[x])
			{
				int y=vv.first,z=vv.second;
				if(d[y]>d[x]+(LL)z)
					d[y]=d[x]+(LL)z,q.push({-d[y],y});
			}
		}
	} 
}

int main()
{
	using namespace TR;
	using namespace GR;
	
	scanf("%d%d%d",&n,&m,&s);
	
	t[0].build(1,1,n,0);  t[1].build(1,1,n,1);
	for(int i=1; i<=n; i++)
		add(t[0].pos[i],t[1].pos[i],0),add(t[1].pos[i],t[0].pos[i],0);
		
	while(m--)
	{
		int op,u,v,w,l,r;
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d",&u,&v,&w);
			add(t[1].pos[u],t[0].pos[v],w);
		}
		else if(op==2)
		{
			scanf("%d%d%d%d",&u,&l,&r,&w);
			t[0].addt(1,1,n,l,r,t[1].pos[u],w,0);
		}
		else
		{
			scanf("%d%d%d%d",&u,&l,&r,&w);
			t[1].addt(1,1,n,l,r,t[0].pos[u],w,1);
		}
	}
	
	dijkstra();
	
	for(int i=1; i<=n; i++)
	{
		LL res=d[t[0].pos[i]];
		if(res>=1e18)
			printf("-1 ");
		else
			printf("%lld ",res);
	}

	return 0;
}

code
//P6348
#include<bits/stdc++.h>
#define pii pair<int,int>
#define pli pair<long long,int>
#define LL long long
using namespace std;

const int N=500010;

int n,m,s;
vector <pii> g[9*N];

void add(int x,int y,int z)
{
	g[x].push_back({y,z});
}

namespace TR
{
	#define lc(p) p*2
	#define rc(p) p*2+1
	int cnt;
	
	struct SegmentTree
	{
		int id[4*N],pos[N];
		
		void build(int p,int l,int r,int flag)
		{
			id[p]=++cnt;
			if(l==r)
			{
				pos[l]=cnt;
				return;
			}
			int mid=(l+r)>>1;
			build(lc(p),l,mid,flag);  build(rc(p),mid+1,r,flag);
			if(!flag)
				add(id[p],id[lc(p)],0),add(id[p],id[rc(p)],0);
			else
				add(id[lc(p)],id[p],0),add(id[rc(p)],id[p],0);
		}
		
		void addt(int p,int l,int r,int ql,int qr,int u,int w,int flag)
		{
			if(ql<=l && qr>=r)
			{
				if(!flag)
					add(u,id[p],w);
				else
					add(id[p],u,w);
				return;
			}
			int mid=(l+r)>>1;
			if(ql<=mid)
				addt(lc(p),l,mid,ql,qr,u,w,flag);
			if(qr>mid)
				addt(rc(p),mid+1,r,ql,qr,u,w,flag);
		}
	}t[2];
}

namespace GR
{
	LL d[9*N];  bool v[9*N];
	priority_queue <pli> q;
	
	void dijkstra()
	{
		memset(d,0x3f,sizeof(d));
		d[TR::t[1].pos[s]]=0;  q.push({0,TR::t[1].pos[s]}); 
		while(q.size())
		{
			int x=q.top().second;  q.pop();
			if(v[x])
				continue;
			v[x]=1;
			for(auto vv:g[x])
			{
				int y=vv.first,z=vv.second;
				if(d[y]>d[x]+(LL)z)
					d[y]=d[x]+(LL)z,q.push({-d[y],y});
			}
		}
	} 
}

int main()
{
	using namespace TR;
	using namespace GR;
	
	scanf("%d%d%d",&n,&m,&s);
	
	t[0].build(1,1,n,0);  t[1].build(1,1,n,1);
	for(int i=1; i<=n; i++)
		add(t[0].pos[i],t[1].pos[i],0),add(t[1].pos[i],t[0].pos[i],0);
		
	while(m--)
	{
		int a,b,c,d;
		scanf("%d%d%d%d",&a,&b,&c,&d);
		t[1].addt(1,1,n,a,b,++cnt,1,1);
		t[0].addt(1,1,n,c,d,cnt,0,0); 
		t[1].addt(1,1,n,c,d,++cnt,1,1);
		t[0].addt(1,1,n,a,b,cnt,0,0); 
	}
	
	dijkstra();
	
	for(int i=1; i<=n; i++)
		printf("%lld\n",d[t[0].pos[i]]);

	return 0;
}

P5025 [SNOI2017] 炸弹

线段树优化建图+ SCC 缩点

缩点后是个DAG,要求每个点能够到达的所有点的权值总和。直接 dp 是错误的,会算重复一些贡献

因为点燃一个炸弹后他能引燃的一定是段区间,所以每个点 xlo[x],hi[x] 分别表示往左、往右能扩展到哪些区间,可以在DAG上跑一遍 dfs 计算出来,最后统计答案即可

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=500010;
const LL MOD=1e9+7;

int n;
LL ans,P[N],R_[N];
vector <int> g[9*N];

void add(int x,int y)
{
	g[x].push_back(y);
}

namespace TR
{
	#define lc(p) p*2
	#define rc(p) p*2+1
	int cnt,fid[9*N];
	
	struct SegmentTree
	{
		int id[4*N],pos[N];
		
		void build(int p,int l,int r,int flag)
		{
			id[p]=++cnt;  
			if(l==r)
			{
				pos[l]=cnt;  fid[cnt]=l;
				return;
			}
			int mid=(l+r)>>1;
			build(lc(p),l,mid,flag);  build(rc(p),mid+1,r,flag);
			if(!flag)
				add(id[p],id[lc(p)]),add(id[p],id[rc(p)]);
			else
				add(id[lc(p)],id[p]),add(id[rc(p)],id[p]);
		}
		
		void addt(int p,int l,int r,int ql,int qr,int u,int flag)
		{
			if(ql<=l && qr>=r)
			{
				if(!flag)
					add(u,id[p]);
				else
					add(id[p],u);
				return;
			}
			int mid=(l+r)>>1;
			if(ql<=mid)
				addt(lc(p),l,mid,ql,qr,u,flag);
			if(qr>mid)
				addt(rc(p),mid+1,r,ql,qr,u,flag);
		}
	}t[2];
}

namespace GR
{
	const int NN=9*N;
	
	int dfn[NN],low[NN],num;
	int sta[NN],top,ins[NN],c[NN],tot;
	int lo[NN],hi[NN];
	bool vis[NN];
	vector <int> gc[NN];
	
	void tarjan(int x)
	{
		dfn[x]=low[x]=++num;
		sta[++top]=x;  ins[x]=1;
		
		for(auto y:g[x])
		{
			if(!dfn[y])
			{
				tarjan(y);
				low[x]=min(low[x],low[y]);
			}
			else if(ins[y])
				low[x]=min(low[x],dfn[y]);
		}
		
		if(dfn[x]==low[x])
		{
			tot++;  lo[tot]=1e9;  
			int y;
			do
			{
				y=sta[top--];  ins[y]=0;
				c[y]=tot;  
				if(TR::fid[y])
					lo[tot]=min(lo[tot],TR::fid[y]),hi[tot]=max(hi[tot],TR::fid[y]);
			}while(x!=y);
		}
	}

	void addc(int x,int y)
	{
		gc[x].push_back(y);
	}
	
	void dfs(int x)
	{
		vis[x]=1;
		for(auto y:gc[x])
		{
			if(!vis[y])
				dfs(y);
			lo[x]=min(lo[x],lo[y]);
			hi[x]=max(hi[x],hi[y]);
		}
		return; 
	}
}

int main()
{
	using namespace TR;
	using namespace GR;
	
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%lld%lld",&P[i],&R_[i]);
	
	t[0].build(1,1,n,0);  t[1].build(1,1,n,1);
	for(int i=1; i<=n; i++)
		add(t[0].pos[i],t[1].pos[i]),add(t[1].pos[i],t[0].pos[i]);
	
	P[n+1]=4e18;
	for(int i=1; i<=n; i++)
	{
		int l=lower_bound(P+1,P+1+n,P[i]-R_[i])-P;
		int r=upper_bound(P+1,P+1+n,P[i]+R_[i])-P-1;
		t[0].addt(1,1,n,l,r,t[1].pos[i],0);
	}
	
	for(int i=1; i<=cnt; i++)
		if(!dfn[i])
			tarjan(i);
			
	for(int i=1; i<=cnt; i++)
		for(auto j:g[i])
			if(c[i]!=c[j])
				addc(c[i],c[j]);
	for(int i=1; i<=tot; i++)
		if(!vis[i])
			dfs(i);
				
	for(int i=1; i<=n; i++)
	{
		int cur=c[t[1].pos[i]];
		(ans+=1LL*i*(hi[cur]-lo[cur]+1))%=MOD;
	}
	
	printf("%lld",ans);

	return 0;
}

P3588 [POI2015] PUS

输入的 k 个数将区间 [l,r] 划分成 k+1 个区间,将这些区间向这 k 个数分别连边,然后拓扑排序即可。如果有环或者无法达成条件就无解

code
#include<bits/stdc++.h>
#define LL long long
#define pii pair<int,int>
using namespace std;

const int N=100010;

int n,s,m,a[N],P[N],deg[5*N];
vector <pii> g[5*N];

void NIE()
{
	puts("NIE");
	exit(0);
}

void add(int x,int y,int z)
{
	g[x].push_back({y,z});
	deg[y]++;
}

namespace TR
{
	#define lc(p) p*2
	#define rc(p) p*2+1
	int cnt,fid[5*N];
	
	struct SegmentTree
	{
		int id[4*N],pos[N];
		
		void build(int p,int l,int r)
		{
			id[p]=++cnt;  
			if(l==r)
			{
				pos[l]=cnt;  fid[cnt]=l;
				return;
			}
			int mid=(l+r)>>1;
			build(lc(p),l,mid);  build(rc(p),mid+1,r);
			add(id[lc(p)],id[p],0);  add(id[rc(p)],id[p],0);
		}
		
		void addt(int p,int l,int r,int ql,int qr,int u)
		{
			if(ql<=l && qr>=r)
			{
				add(id[p],u,0);
				return;
			}
			int mid=(l+r)>>1;
			if(ql<=mid)
				addt(lc(p),l,mid,ql,qr,u);
			if(qr>mid)
				addt(rc(p),mid+1,r,ql,qr,u);
		}
	}t;
}

namespace GR
{
	int val[5*N];
	
	void topsort()
	{
		queue <int> q;
		
		for(int i=1; i<=TR::cnt; i++)
		{
			if(!deg[i])
				q.push(i),val[i]=1;
		} 
		while(q.size())
		{
			int x=q.front();  q.pop();  //cout<<x<<" "<<val[x]<<endl;
			if(TR::fid[x] && a[TR::fid[x]])
			{
				int v=TR::fid[x];  //cout<<"kk"<<v<<endl;
				if(val[x]<=a[v])
					val[x]=a[v];
				else
					NIE();
			}
			if(val[x]>1e9)
				NIE();
			
			for(auto v:g[x])
			{
				int y=v.first,z=v.second;
				deg[y]--;
				val[y]=max(val[y],val[x]+z);
				if(!deg[y])
					q.push(y);
			}
		}
		
		for(int i=1; i<=TR::cnt; i++)
			if(deg[i])
				NIE();		
	}
}

int main()
{
	using namespace TR;
	using namespace GR;
	
	scanf("%d%d%d",&n,&s,&m);
	for(int i=1; i<=s; i++)
	{
		int x;
		scanf("%d",&x);
		scanf("%d",&a[x]);
	}
	
	t.build(1,1,n);
	
	for(int i=1; i<=m; i++)
	{
		int k,l,r,lur,cur;
		scanf("%d%d%d",&l,&r,&k);
		
		P[0]=l-1;  cnt++;
		for(int j=1; j<=k; j++)
		{
			scanf("%d",&P[j]);
			if(P[j-1]+1<=P[j]-1)
				t.addt(1,1,n,P[j-1]+1,P[j]-1,cnt);
		}
		if(P[k]+1<=r)
			t.addt(1,1,n,P[k]+1,r,cnt);
		for(int j=1; j<=k; j++)
			add(cnt,t.pos[P[j]],1); 
	}
	
	topsort();
	
	puts("TAK");
	for(int i=1; i<=n; i++)
		printf("%d ",val[t.pos[i]]);

	return 0;
}

二、倍增 / ST 表优化建图

P5344 【XR-1】逛森林

倍增优化建图,每个点向上的 20t 级祖先连边,分为内向和外向

这样点数是 O(nlogn) 的,边数是 ((n+m)logn) 的,加上 Dijkstra 的时间复杂度就是 O(n(n+m)log2n)

code
#include<bits/stdc++.h>
#define mp make_pair
using namespace std;

const int N=50010,M=1000010,NN=4000010,MM=24000010;

int n,m,s,t,cnt,id;
int f[N][20],dep[N],in[N][20],out[N][20],d[NN];
struct node{int u1,v1,u2,v2,w;}e[M];
int head[NN],ver[MM],edge[MM],nxt[MM],tot;
bool v[NN],vis[NN];
vector <int> g[N]; 

void add(int x,int y,int z)
{
	ver[++tot]=y;  edge[tot]=z;
	nxt[tot]=head[x];  head[x]=tot;
}

struct DS
{
	int fa[N];
	
	void init()
	{
		for(int i=1; i<=n; i++)
			fa[i]=i;
	}
	
	int get(int x)
	{
		if(x==fa[x])
			return x;
		return fa[x]=get(fa[x]);
	}
}ds;

void dfs(int x,int fa)
{
	dep[x]=dep[f[x][0]=fa]+1;
	in[x][0]=++id;  add(id,x,0);  add(id,fa,0);
	out[x][0]=++id;  add(x,id,0);  add(fa,id,0);
	for(int i=1; i<=t; i++)
	{
		f[x][i]=f[f[x][i-1]][i-1];
		in[x][i]=++id;  add(id,in[x][i-1],0);  add(id,in[f[x][i-1]][i-1],0);
		out[x][i]=++id;  add(out[x][i-1],id,0);  add(out[f[x][i-1]][i-1],id,0);
	}
	
	for(int i=0; i<g[x].size(); i++)
	{
		int y=g[x][i];
		if(y==fa)
			continue;
		dfs(y,x);
	}
}

void LCA1(int x,int y,int k)
{
	if(dep[x]<dep[y])
		swap(x,y);
	add(y,k,0);
	for(int i=t; i>=0; i--)
		if(dep[f[x][i]]>=dep[y])
			add(out[x][i],k,0),x=f[x][i];
	if(x==y)
		return;
	for(int i=t; i>=0; i--)
		if(f[x][i]!=f[y][i])
			add(out[x][i],k,0),add(out[y][i],k,0),x=f[x][i],y=f[y][i];
	add(out[x][0],k,0);
	return;
}

void LCA2(int x,int y,int k)
{
	if(dep[x]<dep[y])
		swap(x,y);
	add(k,y,0);
	for(int i=t; i>=0; i--)
		if(dep[f[x][i]]>=dep[y])
			add(k,in[x][i],0),x=f[x][i];
	if(x==y)
		return;
	for(int i=t; i>=0; i--)
		if(f[x][i]!=f[y][i])
			add(k,in[x][i],0),add(k,in[y][i],0),x=f[x][i],y=f[y][i];
	add(k,in[x][0],0);
}

void dijkstra()
{
	memset(d,0x3f,sizeof(d));
	priority_queue < pair<int,int> > q; 
	q.push(mp(0,s));  d[s]=0;
	
	while(q.size())
	{
		int x=q.top().second;  q.pop();
		if(v[x])
			continue;
		v[x]=1;
		for(int i=head[x]; i; i=nxt[i])
		{
			int y=ver[i],z=edge[i];
			if(d[y]>d[x]+z)
			{
				d[y]=d[x]+z;
				q.push(mp(-d[y],y));
			}
		}
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	ds.init();  id=n;
	for(int i=1; i<=m; i++)
	{
		int op,x,y,z,u1,v1,u2,v2,w;
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d%d%d",&u1,&v1,&u2,&v2,&w);
			if(ds.get(u1)!=ds.get(v1) || ds.get(u2)!=ds.get(v2))
				continue;
			e[++cnt]=(node){u1,v1,u2,v2,w};
			
		}
		else
		{
			scanf("%d%d%d",&x,&y,&z);
			int fx=ds.get(x),fy=ds.get(y);
			if(fx==fy)
				continue;
			g[x].push_back(y);  g[y].push_back(x);
			add(x,y,z);  add(y,x,z);  ds.fa[fx]=fy;
		}
	}
	
	t=log(n)/log(2);
	for(int i=1; i<=n; i++)
		if(ds.fa[i]==i)
			dfs(i,0);
	
	for(int i=1; i<=cnt; i++)
	{
		LCA1(e[i].u1,e[i].v1,++id);
		LCA2(e[i].u2,e[i].v2,++id);
		add(id-1,id,e[i].w);
	}
	
	dijkstra();
	
	for(int i=1; i<=n; i++)
		printf("%d ",d[i]>=1e9? -1:d[i]);

	return 0;
}

考虑到题目只要求跑最短路,所以可以像 ST 表一样,重复建边,边数就是 O(nlogn) 的,总的时间复杂度 O(nlog2n)

code
#include<bits/stdc++.h>
#define mp make_pair
using namespace std;

const int N=50010,M=1000010,NN=4000010,MM=20000010;

int n,m,s,t,cnt,id;
int f[N][20],dep[N],in[N][20],out[N][20],d[NN];
struct node{int u1,v1,u2,v2,w;}e[M];
int head[NN],ver[MM],edge[MM],nxt[MM],tot;
bool v[NN],vis[NN];
vector <int> g[N]; 

void add(int x,int y,int z)
{
	ver[++tot]=y;  edge[tot]=z;
	nxt[tot]=head[x];  head[x]=tot;
}

struct DS
{
	int fa[N];
	
	void init()
	{
		for(int i=1; i<=n; i++)
			fa[i]=i;
	}
	
	int get(int x)
	{
		if(x==fa[x])
			return x;
		return fa[x]=get(fa[x]);
	}
}ds;

void dfs(int x,int fa)
{
	dep[x]=dep[f[x][0]=fa]+1;
	in[x][0]=++id;  add(id,x,0);  add(id,fa,0);
	out[x][0]=++id;  add(x,id,0);  add(fa,id,0);
	for(int i=1; i<=t; i++)
	{
		f[x][i]=f[f[x][i-1]][i-1];
		in[x][i]=++id;  add(id,in[x][i-1],0);  add(id,in[f[x][i-1]][i-1],0);
		out[x][i]=++id;  add(out[x][i-1],id,0);  add(out[f[x][i-1]][i-1],id,0);
	}
	
	for(int i=0; i<g[x].size(); i++)
	{
		int y=g[x][i];
		if(y==fa)
			continue;
		dfs(y,x);
	}
}

int LCA(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=t; i>=0; i--)
		if(dep[f[x][i]]>=dep[y])
			x=f[x][i];
	if(x==y)
		return x;
	for(int i=t; i>=0; i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}

void link1(int x,int lca,int k)
{
    if(x==lca)
    {
        add(x,k,0);
        return;
    }
 	int tt=log(dep[x]-dep[lca])/log(2);
 	add(out[x][tt],k,0);
 	for(int i=tt; i>=0; i--)
 		if(dep[f[x][i]]-(1<<tt)>=dep[lca])
 			x=f[x][i];
 	add(out[x][tt],k,0);
}

void link2(int x,int lca,int k)
{
    if(x==lca)
    {
        add(k,x,0);
        return;
    }
 	int tt=log(dep[x]-dep[lca])/log(2);
 	add(k,in[x][tt],0);
 	for(int i=tt; i>=0; i--)
 		if(dep[f[x][i]]-(1<<tt)>=dep[lca])
 			x=f[x][i];
 	add(k,in[x][tt],0);
}

void dijkstra()
{
	memset(d,0x3f,sizeof(d));
	priority_queue < pair<int,int> > q; 
	q.push(mp(0,s));  d[s]=0;
	
	while(q.size())
	{
		int x=q.top().second;  q.pop();
		if(v[x])
			continue;
		v[x]=1;
		for(int i=head[x]; i; i=nxt[i])
		{
			int y=ver[i],z=edge[i];
			if(d[y]>d[x]+z)
			{
				d[y]=d[x]+z;
				q.push(mp(-d[y],y));
			}
		}
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	ds.init();  id=n;
	for(int i=1; i<=m; i++)
	{
		int op,x,y,z,u1,v1,u2,v2,w;
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d%d%d",&u1,&v1,&u2,&v2,&w);
			if(ds.get(u1)!=ds.get(v1) || ds.get(u2)!=ds.get(v2))
				continue;
			e[++cnt]=(node){u1,v1,u2,v2,w};
			
		}
		else
		{
			scanf("%d%d%d",&x,&y,&z);
			int fx=ds.get(x),fy=ds.get(y);
			if(fx==fy)
				continue;
			g[x].push_back(y);  g[y].push_back(x);
			add(x,y,z);  add(y,x,z);  ds.fa[fx]=fy;
		}
	}
	
	t=log(n)/log(2)+1;
	for(int i=1; i<=n; i++)
		if(ds.fa[i]==i)
			dfs(i,0);
	
	for(int i=1; i<=cnt; i++)
	{
		int lca=LCA(e[i].u1,e[i].v1);
 		link1(e[i].u1,lca,++id);  link1(e[i].v1,lca,id);
		lca=LCA(e[i].u2,e[i].v2);
 		link2(e[i].u2,lca,++id);  link2(e[i].v2,lca,id);
		add(id-1,id,e[i].w);
	}
	
	dijkstra();
	
	for(int i=1; i<=n; i++)
		printf("%d ",d[i]>=1e9? -1:d[i]);

	return 0;
}

三、其它技巧

P6822 [PA2012] Tax

可以发现每个点的贡献都和与它连接的边有关,尝试将贡献全部归到入边

考虑边转点,将一条边拆成 i,i 两个点,并在它们之间连边权为原图边权的边

对于一个点,设与它连接的边为 e1,e2,,ek。考虑入边 ei 和出边 ej,若 edge(ei)<edge(ej),则要多出贡献 egde(ej)edge(ei),若 edge(ei)edge(ej) 则没事。所以想到一种建边方法,对所有 edge(ei)<edge(ej),从 eiej 连边权为 edge(ej)edge(ei) 的边,从 ejei 连边权为 0 的边

但这样建边是 O(m2) 的,由于边权是差值,所以可以将一个点的所有边从小到大排序,差分建边,这样就是 O(m)

code
#include<bits/stdc++.h>
#define LL long long
#define pii pair<int,int>
#define pli pair<LL,int>
using namespace std;

const int N=100010,M=200010;

int n,m;
struct node{int y,z,id;};vector <node> e[M]; 
vector <pii> g[2*M];
LL d[2*M];
bool v[2*M];

void dijkstra()
{
	memset(d,0x3f,sizeof(d));
	priority_queue <pli> q; 
	q.push({0,0});  d[0]=0;
	
	while(q.size())
	{
		int x=q.top().second;  q.pop();
		if(v[x])
			continue;
		v[x]=1;
		for(auto v:g[x])
		{
			int y=v.first,z=v.second;
			if(d[y]>d[x]+z)
			{
				d[y]=d[x]+z;
				q.push({-d[y],y});
			}
		}
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		e[x].push_back({y,z,i});
		e[y].push_back({x,z,i+m}); 
		g[i].push_back({i+m,z});
		g[i+m].push_back({i,z});
	}
	
	for(auto v:e[1])
		g[0].push_back({v.id,v.z});
	for(int i=2; i<=n; i++)
	{
		sort(e[i].begin(),e[i].end(),[&](node x,node y){return x.z<y.z;});
		int len=e[i].size();
		for(int j=0; j<len-1; j++)
		{
			node u=e[i][j],v=e[i][j+1];
			g[u.id].push_back({v.id,v.z-u.z});
			g[v.id].push_back({u.id,0});
		}
	}
	
	dijkstra();
	
	LL ans=1e18;
	for(auto v:e[n])
		ans=min(ans,d[v.id]); 
	
	printf("%lld",ans);
	
	return 0;
}
posted @   xishanmeigao  阅读(295)  评论(0编辑  收藏  举报
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示