图论训练记录 2025.1

牛场围栏

https://www.gxyzoj.com/d/hzoj/p/LG2662

如果暴力dp,有一定的概率会T,如何降低时间复杂度?

前置知识:同余最短路

例题:P3403 跳楼机

假设x<y<z,当前值对x取模为i

则通过操作,可以变为(i+y)%x(i+z)%x

所以,可以将能相互转移的点连边,边权即加上的数

此时,从起点到这个点的最短路即为对x取模余i的最小可用x,y,z凑出的数

答案为:

i=0x1(hdix+1)

注意,因为下标从1开始,要统一先-1

点击查看代码
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
ll h,inf=(1ll<<62)+((1ll<<62)-1),d[100005];
int a,b,c,head[100005],edgenum;
struct edge{
	int to,nxt,val;
}e[400005];
void add_edge(int u,int v,int w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].val=w;
	e[edgenum].to=v;
	head[u]=edgenum;
}
queue<int> q;
bool inq[100005];
void spfa(int x)
{
	q.push(x);
	d[x]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(d[v]>d[u]+e[i].val)
			{
				d[v]=d[u]+e[i].val;
				if(!inq[v])
				{
					inq[v]=1;
					q.push(v);
				}
			}
		}
	}
}
int main()
{
	scanf("%lld%d%d%d",&h,&a,&b,&c);
	if(a==1||b==1||c==1)
	{
		printf("%lld",h);
		return  0;
	}
	h--;
	for(int i=0;i<a;i++)
	{
		add_edge(i,(i+b)%a,b);
		add_edge(i,(i+c)%a,c);
		d[i]=inf;
	}
	spfa(0);
	ll ans=0;
	for(int i=0;i<a;i++)
	{
		if(h>=d[i]) ans+=(h-d[i])/a+1;
	}
	printf("%lld",ans);
	return 0;
}

而对于这个题,数字变多了,用同样的思路,先连边,跑最短路

此时,得到的是最少的可行解,减去模数就是答案,注意跑不到直接输-1

点击查看代码
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,minx=1e9,d[3005],head[3005],edgenum;
bool vis[3005];
struct edge{
	int to,nxt,val;
}e[9000001];
void add_edge(int u,int v,int w)
{
//	printf("%d %d %d\n",u,v,w);
	e[++edgenum].nxt=head[u];
	e[edgenum].val=w;
	e[edgenum].to=v;
	head[u]=edgenum;
}
queue<int> q;
bool inq[3005];
void spfa(int x)
{
	q.push(x);
	inq[x]=1;
	d[x]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(d[v]>d[u]+e[i].val)
			{
				d[v]=d[u]+e[i].val;
				if(!inq[v])
				{
					inq[v]=1;
					q.push(v);
				}
			}
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		for(int j=0;j<=min(x,m);j++)
		{
			vis[x-j]=1;
		}
		minx=min(minx,x-m);
	}
	minx=max(minx,0);
	if(vis[1])
	{
		printf("-1");
		return 0;
	}
	for(int i=0;i<minx;i++)
	{
		for(int j=minx+1;j<=3000;j++)
		{
			if(vis[j])
			{
//				if(i==1) printf("%d ",j);
				add_edge(i,(i+j)%minx,j);
			}
		}
		d[i]=1e9;
	}
	spfa(0);
	int ans=0;
	for(int i=0;i<minx;i++)
	{
//		printf("%d ",d[i]);
		ans=max(ans,d[i]-minx);
	}
	ans=max(ans,0);
	if(ans==1e9) printf("-1");
	else printf("%d",ans);
	return 0;
}

「LNOI2014」LCA

https://www.gxyzoj.com/d/hzoj/p/P1241

因为一个点的lca一定在它到根的路径上,而且是求深度

所以可以将每个要求的点到根的路径直接+1,树剖实现即可

对于区间,其实可以拆成[1,l1][1,r],将两个点标记,然后按1到n的顺序求解即可

点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
using namespace std;
const int mod=201314;
int n,m,edgenum,head[50005];
struct edge{
	int to,nxt;
}e[100005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int son[50005],id[50005],siz[50005],top[50005],f[50005];
int dep[50005],cnt,rnk[50005];
void dfs(int u,int fa)
{
	f[u]=fa,siz[u]=1,dep[u]=dep[fa]+1;
	int maxn=-1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		dfs(v,u);
		siz[u]+=siz[v];
		if(maxn<siz[v])
		{
			maxn=siz[v],son[u]=v;
		}
	}
}
void dfs1(int u,int t)
{
	top[u]=t,id[u]=++cnt;
	rnk[cnt]=id[u];
	if(son[u])
	{
		dfs1(son[u],t);
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==f[u]||v==son[u]) continue;
		dfs1(v,v);
	}
}
struct seg_tr{
	int l,r,sum,lazy;
}tr[800005];
void build(int id,int l,int r)
{
	tr[id].l=l,tr[id].r=r;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
}
void pushdown(int id)
{
	if(tr[id].lazy)
	{
		tr[lid].sum+=(tr[lid].r-tr[lid].l+1)*tr[id].lazy%mod;
		tr[lid].lazy+=tr[id].lazy;
		tr[rid].sum+=(tr[rid].r-tr[rid].l+1)*tr[id].lazy%mod;
		tr[rid].lazy+=tr[id].lazy;
		tr[lid].sum%=mod,tr[rid].sum%=mod;
		tr[id].lazy=0;
	}
}
void update(int id,int l,int r)
{
	if(tr[id].l==l&&tr[id].r==r)
	{
		tr[id].sum+=r-l+1;
		tr[id].sum%=mod;
		tr[id].lazy++;
		return;
	}
	pushdown(id);
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) update(lid,l,r);
	else if(l>mid) update(rid,l,r);
	else update(lid,l,mid),update(rid,mid+1,r);
	tr[id].sum=(tr[lid].sum+tr[rid].sum)%mod;
}
void add(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(1,id[top[u]],id[u]);
		u=f[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	update(1,id[v],id[u]);
}
int query(int id,int l,int r)
{
//	printf("%d %d %d %d %d\n",id,tr[id].l,tr[id].r,l,r);
	if(tr[id].l==l&&tr[id].r==r)
	{
		return tr[id].sum;
	}
	pushdown(id);
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) return query(lid,l,r);
	else if(l>mid) return query(rid,l,r);
	else return (query(lid,l,mid)+query(rid,mid+1,r))%mod;
}
int getsum(int u,int v)
{
	int res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res=(res+query(1,id[top[u]],id[u]))%mod;
		u=f[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res=(res+query(1,id[v],id[u]))%mod;
	return res;
}
struct node{
	int id,x,t;
};
vector<node> v[50005];
int ans[50005];
int main()
{
//	freopen("1.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		x++;
		add_edge(x,i);
	}
	dfs(1,0);
	dfs1(1,1);
	build(1,1,n);
//	printf("1");
	for(int i=1;i<=m;i++)
	{
		int l,r,z;
		scanf("%d%d%d",&l,&r,&z);
		v[l].push_back((node){i,z+1,-1});
		v[r+1].push_back((node){i,z+1,1});
	}
	for(int i=1;i<=n;i++)
	{
		add(i,1);
//		printf("%d\n",i);
		for(int j=0;j<v[i].size();j++)
		{
			int x=v[i][j].x,id1=v[i][j].id;
			ans[id1]=(ans[id1]+getsum(1,x)*v[i][j].t)%mod;
		}
	}
	for(int i=1;i<=m;i++)
	{
		printf("%d\n",(ans[i]+mod)%mod);
	}
	return 0;
}

跳跳棋

神秘构造题

可以发现,假设当前位置为(x,y,z),且x<y<z,则能转移到如下这4个位置(按大小排序):

(2xy,x,z),(x,z,2zy),(y,2yx,z),(x,2yz,y)

可以发现,其中的两个对总距离是缩小,而另外两个是增加

先考虑缩小,因为(y,2yx,z),(x,2yz,y)这两个状态是对立的,当yx>zy执行第1个,反之,执行第2个

但是因为yx=zy时,左右端点均无法移动

因为操作可逆,所以可以将初状态和末状态化简到yx=zy,再继续求解

因为接下来只存在两种情况,即中间移到左边或右边,可以按照转移构建二叉树

此时,初状态和末状态必然为其上两个节点,而他们到lca的距离之和就是答案

但因为状态很多,无法建树,可以现将多出的运算推平后,二分次数

点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
	int x,y,z,tot;
}t1,t2;
int cnt;
void get(int x,int y,int z)
{
	cnt=0;
	while(1)
	{
		int d1=y-x,d2=z-y,d3;
		if(d1==d2) break;
		if(d1>d2)
		d3=(d1-1)/d2,cnt+=d3,y-=d3*d2,z-=d3*d2;
		else
		d3=(d2-1)/d1,cnt+=d3,x+=d3*d1,y+=d3*d1;
	}
	if(t1.x||t1.y) t2=(node){x,y,z,cnt};
	else t1=(node){x,y,z,cnt};
}
bool check(int x,int ax,int ay,int az,int bx,int by,int bz)
{
	int y=x;
	while(x)
	{
		int d1=ay-ax,d2=az-ay,d3;
		if(d1==d2) break;
		if(d1>d2)
		d3=min(x,(d1-1)/d2),x-=d3,ay-=d3*d2,az-=d3*d2;
		else
		d3=min(x,(d2-1)/d1),x-=d3,ax+=d3*d1,ay+=d3*d1;
	}
	while(y)
	{
		int d1=by-bx,d2=bz-by,d3;
		if(d1==d2) break;
		if(d1>d2)
		d3=min(y,(d1-1)/d2),y-=d3,by-=d3*d2,bz-=d3*d2;
		else
		d3=min(y,(d2-1)/d1),y-=d3,bx+=d3*d1,by+=d3*d1;
	}
	if(ax==bx&&ay==by&&az==bz)
	{
		return 1;
	}
	else return 0;
}
int a[10],x,y,z,ax,ay,az;
int main()
{
	for(int i=1;i<=3;i++) scanf("%d",&a[i]);
	sort(a+1,a+4);
	x=a[1],y=a[2],z=a[3];
	for(int i=1;i<=3;i++) scanf("%d",&a[i]);
	sort(a+1,a+4);
	ax=a[1],ay=a[2],az=a[3];
	get(x,y,z);
	get(ax,ay,az);
	if(t1.x!=t2.x||t1.y!=t2.y||t1.z!=t2.z)
	{
//		printf("%d %d %d %d %d %d\n",t1.x,t1.y,t1.z,t2.x,t2.y,t2.z);
		printf("NO");
		return 0;
	}
	if(t1.tot<t2.tot)
	{
		swap(t1,t2),swap(x,ax),swap(y,ay),swap(z,az);
	}
	int ans=t1.tot-t2.tot;
	cnt=ans;
	while(cnt)
	{
		int d1=y-x,d2=z-y,d3;
		if(d1==d2) break;
		if(d1>d2)
		d3=min(cnt,(d1-1)/d2),cnt-=d3,y-=d3*d2,z-=d3*d2;
		else
		d3=min(cnt,(d2-1)/d1),cnt-=d3,x+=d3*d1,y+=d3*d1;
	}
	int l=0,r=t2.tot;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid,x,y,z,ax,ay,az))
		{
			r=mid;
		}
		else l=mid+1;
	}
	printf("YES\n%d",ans+2*l);
	return 0;
}

Berland and the Shortest Paths

https://www.gxyzoj.com/d/hzoj/p/CF1005F

原,dij跑出最短路然后倒推统计情况即可

点击查看代码
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
int n,m,k,head[200005],edgenum;
struct edge{
	int to,nxt,id;
}e[400005];
void add_edge(int u,int v,int id)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].id=id;
	e[edgenum].to=v;
	head[u]=edgenum;
}
struct node{
	int x,y,val;
	bool operator < (const node &tmp)const{
		return val>tmp.val;
	}
};
priority_queue<node> q;
int d[200005];
bool vis[200004];
void dijkstra(int x)
{
	q.push((node){x,x,0});
	while(!q.empty())
	{
		int u=q.top().y,val=q.top().val;
		q.pop();
		if(!vis[u])
		{
			vis[u]=1;
			d[u]=val;
			for(int i=head[u];i;i=e[i].nxt)
			{
				int v=e[i].to;
				if(!vis[v])
				{
					q.push((node){u,v,val+1});
				}
			}
		}
	}
}
bool v1[200005];
int cnt;
ll sum;
void dfs(int u)
{
	if(u==n+1)
	{
		for(int i=1;i<=m;i++)
		{
			if(v1[i]) printf("1");
			else printf("0");
		}
		printf("\n");
		cnt++;
		return;
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(d[u]==d[v]+1)
		{
			v1[e[i].id]=1;
			dfs(u+1);
			v1[e[i].id]=0;
			if(cnt==sum) return;
		}
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);//
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v,i);
		add_edge(v,u,i);
	}
	dijkstra(1);
	sum=1;
	for(int i=2;i<=n;i++)
	{
		cnt=0;
		for(int j=head[i];j;j=e[j].nxt)
		{
			int v=e[j].to;
			if(d[i]==d[v]+1) cnt++;
		}
		sum=min(1ll*k,1ll*sum*cnt);
	}
	cnt=0;
	printf("%lld\n",sum);
	dfs(2);
	return 0;
}

Breaking Good

https://www.gxyzoj.com/d/hzoj/p/4469

和上一题一样,另外加一个统计路径上1的个数,并使在满足距离最短的情况下让它尽量大

此时,和上一题一样找来源即可

点击查看代码
#include<cstdio>
#include<queue>
using namespace std;
int n,m,head[100005],edgenum=1,cnt;
struct edge{
	int nxt,to,val,from;
}e[200005];
void add_edge(int u,int v,int w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].from=u;
	e[edgenum].to=v;
	e[edgenum].val=w;
	head[u]=edgenum;
}
queue<int> q;
int d[100005],d1[100005];
bool inq[100004];
void spfa(int x)
{
	q.push(x);
	d[x]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(d[v]>d[u]+1||(d[v]==d[u]+1&&d1[v]<d1[u]+e[i].val))
			{
				d[v]=d[u]+1;
				d1[v]=d1[u]+e[i].val;
				if(!inq[v])
				{
					inq[v]=1;
					q.push(v);
				}
			}
		}
	}
}
int vis[200005];
void dfs(int u)
{
	if(u==1) return;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to,w=e[i].val;
		if(vis[i]) continue;
		if(d[v]+1==d[u]&&d1[v]+w==d1[u])
		{
			if(w) vis[i]=vis[i^1]=1;
			else vis[i]=vis[i^1]=2;
			dfs(v);
			break;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
		cnt+=w;
	}
	for(int i=1;i<=n;i++)
	{
		d[i]=1e9;
	}
	spfa(1);
	printf("%d\n",d[n]-d1[n]+cnt-d1[n]);
	dfs(n);
	for(int i=2;i<=edgenum;i++)
	{
		if(!vis[i])
		{
			if(e[i].val) vis[i]=vis[i^1]=3;
		}
	}
	for(int i=2;i<=edgenum;i+=2)
	{
		if(vis[i]>1)
		printf("%d %d %d\n",e[i].from,e[i].to,!e[i].val);
	}
	return 0;
}

Turtle and Intersected Segments

https://www.gxyzoj.com/d/hzoj/p/4470

如果暴力判断,边会很多,考虑什么样的边是必要的

首先,假设三个相互重叠的区间,权值a1>a2>a3,那么保留的边一定是(a1,a2)(a2,a3)

更多同理,所以,当一些区间相互重叠时,新加入的点只需要向这些权值中比它笑的点中最大的和比它大的点中最小的连边

因为两个相交的区间必然有一个端点在另一个区间上

所以直接将端点取出,然后依次处理

因为是要找前驱后继,考虑使用set,而区间在l时加入对应的a,在r时删除即可

最后跑kruskal,注意统计边数判-1

点击查看代码
#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
#define ll long long
#define it set<int>::iterator
using namespace std;
int n,T,b[1000005],c[500005],cnt[500005],mp[500005],ecnt;
struct node{
	int l,r,val,id;
}a[500005];
vector<node> v[1000005];
set<int> st;
struct edge{
	int u,v;
	ll val;
}e[2000005];
int f[500005];
bool cmp(edge x,edge y)
{
	return x.val<y.val;
}
int find(int x)
{
	if(f[x]!=x) f[x]=find(f[x]);
	return f[x];
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		ecnt=0;
		for(int i=1;i<=n;i++)
		{
			int l,r,val;
			scanf("%d%d%d",&l,&r,&val);
			a[i]=(node){l,r,val,i};
			b[i]=l,b[i+n]=r;
			c[i]=val;
			f[i]=i,mp[i]=cnt[i]=0;
		}
		sort(c+1,c+n+1);
		for(int i=1;i<=n;i++)
		{
			a[i].val=lower_bound(c+1,c+n+1,a[i].val)-c;
			int t=a[i].val;
			a[i].val+=cnt[t];
			mp[a[i].val]=i;
			cnt[t]++;
		}
		sort(b+1,b+n*2+1);
		int m=unique(b+1,b+2*n+1)-1-b;
		for(int i=1;i<=m;i++) v[i].clear();
		for(int i=1;i<=n;i++)
		{
			a[i].l=lower_bound(b+1,b+m+1,a[i].l)-b;
			a[i].r=lower_bound(b+1,b+m+1,a[i].r)-b;
			v[a[i].l].push_back((node){1,0,a[i].val,i});
			v[a[i].r].push_back((node){-1,0,a[i].val,i});
		}
		for(int i=1;i<=m;i++)
		{
			for(int j=0;j<v[i].size();j++)
			{
				if(v[i][j].l>0)
				{
					st.insert(v[i][j].val);
				}
			}
			for(int j=0;j<v[i].size();j++)
			{
				int x=v[i][j].val;
				it p=st.upper_bound(x);
				if(p!=st.end())
				{
					e[++ecnt]=(edge){v[i][j].id,mp[*p],c[*p]-c[x]};
				}
				p=st.lower_bound(x);
				if(p!=st.begin())
				{
					p--;
//					if(p!=st.begin())
					e[++ecnt]=(edge){v[i][j].id,mp[*p],c[x]-c[*p]};
				}
			}
			for(int j=0;j<v[i].size();j++)
			{
				if(v[i][j].l<0)
				{
					st.erase(st.find(v[i][j].val));
				}
			}
		}
		sort(e+1,e+ecnt+1,cmp);
		ll ans=0;
		int sum=0;
		for(int i=1;i<=ecnt;i++)
		{
			if(find(e[i].u)!=find(e[i].v))
			{
				f[find(e[i].u)]=find(e[i].v);
				ans+=e[i].val;
				sum++;
			}
		}
		if(sum!=n-1) printf("-1\n");
		else printf("%lld\n",ans);
	}
	return 0;
}

DFS Trees

https://www.gxyzoj.com/d/hzoj/p/4385

有一个性质,边权两两不同的图最小生成树唯一

证明:假设有两棵不同的最小生成树T1,T2,假设边集中,第一个不同的边记为k,假设val1k<val2k
此时,如果在T2中加入边权为val1k的边,必然成环
因为如果这环上的所有边权都满足小于val1k,则T1就不是树
如果不满足,则必然可以删去一条边权更大的边,与假设矛盾

此时,走到一条非树边,就不满足条件,有两种情况:

  • 横插边,u,v子树外的点必然先到它们的lca,此时,因为要走到无法行动才回退,所以走完u或v的子树后,必然经过横插边

  • 返祖边,不在(u,v)路径上的点必然会先走到其中一个点,因为是最小生成树,该边权值大,不会被选择,但路径上的点因为没有路,必然经过

此时,差分即可

对于横插边,根处+1,u,v处-1,对于返祖边,v处+1,是u儿子且是v祖先的点处-1

点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,head[200005],edgenum;
struct edge{
	int nxt,to;
}e[400005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
struct node{
	int u,v,id;
}a[200005];
int f[200005];
bool vis[200005];
int find(int x)
{
	if(x!=f[x]) f[x]=find(f[x]);
	return f[x];
}
int dep[200005],f1[200005][20];
void dfs(int u,int fa)
{
	dep[u]=dep[fa]+1;
	f1[u][0]=fa;
	for(int i=1;i<=19;i++)
	{
		f1[u][i]=f1[f1[u][i-1]][i-1];
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=19;i>=0;i--)
	{
		if(dep[f1[x][i]]>=dep[y])
		{
			x=f1[x][i];
		}
		if(x==y) return x;
	}
	for(int i=19;i>=0;i--)
	{
		if(f1[x][i]!=f1[y][i])
		{
			x=f1[x][i];
			y=f1[y][i];
		}
	}
	return f1[x][0];
}
int p[200005],ans[200005];
void dfs1(int u,int fa)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		p[v]+=p[u];
		dfs1(v,u);
	}
	if(!p[u]) ans[u]=1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		a[i]=(node){u,v,i};
	}
	for(int i=1;i<=n;i++)
	{
		f[i]=i;
	}
	for(int i=1;i<=m;i++)
	{
		if(find(a[i].u)!=find(a[i].v))
		{
			f[find(a[i].u)]=find(a[i].v);
			add_edge(a[i].u,a[i].v);
			add_edge(a[i].v,a[i].u);
			vis[i]=1;
		}
	}
	dfs(1,0);
	for(int i=1;i<=m;i++)
	{
//		printf("%d ",vis[i]);
		if(!vis[i])
		{
			int x=a[i].u,y=a[i].v;
			if(dep[x]>dep[y]) swap(x,y);
			int lc=lca(x,y);
//			printf("%d %d %d\n",x,y,lc);
			if(lc==x)
			{
				p[y]--;
				for(int j=19;j>=0;j--)
				{
					if(dep[x]<dep[f1[y][j]])
					y=f1[y][j];
				}
				p[y]++;
			}
			else
			{
				p[1]++,p[x]--,p[y]--;
			}
		}
	}
	dfs1(1,0);
	for(int i=1;i<=n;i++)
	{
		printf("%d",ans[i]);
	}
	return 0;
}

MST Company

https://www.gxyzoj.com/d/hzoj/p/4471

可以先将不与1相连的边加入,此时,会形成一个最小生成森林

接下来加入与1相邻的边,此时,如果加入超过k条或加入后图不相连,显然无解

接下来考虑加入与1相连的边,考虑到,必然加入后会形成环,找到环上最大值断掉即可

点击查看代码
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int n,m,k,mx[5005];
struct node{
	int u,v,w,id;
}a[100005];
int f[5005],edgenum=1,head[5005];
bool vis[200005],del[100005],vis1[100005];
int find(int x)
{
	if(f[x]!=x) f[x]=find(f[x]);
	return f[x];
}
bool cmp(node x,node y)
{
	return x.w<y.w;
}
vector<int> v1,v2;
struct edge{
	int to,val,nxt,id;
}e[200005];
void add_edge(int u,int v,int w,int id)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	e[edgenum].val=w;
	e[edgenum].id=id;
	head[u]=edgenum;
}
void dfs(int u,int fa)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa||vis[i]||v==1) continue;
		if(e[i].val>=e[mx[u]].val) mx[v]=i;
		else mx[v]=mx[u];
		dfs(v,u);
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		a[i]=(node){u,v,w,i};
	}
	for(int i=1;i<=n;i++)
	{
		f[i]=i;
	}
	sort(a+1,a+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		int u=a[i].u,v=a[i].v;
		if(u==1||v==1) continue;
		if(find(u)!=find(v))
		{
			f[find(u)]=find(v);
			v1.push_back(a[i].id);
			add_edge(u,v,a[i].w,a[i].id);
			add_edge(v,u,a[i].w,a[i].id);
		}
	}
	for(int i=1;i<=m;i++)
	{
		int u=a[i].u,v=a[i].v;
		if(u!=1&&v!=1) continue;
		if(find(u)!=find(v))
		{
			f[find(u)]=find(v),k--;
			v1.push_back(a[i].id);
			add_edge(u,v,a[i].w,a[i].id);
			add_edge(v,u,a[i].w,a[i].id);
		}
		else v2.push_back(i);
	}
	for(int i=2;i<=n;i++)
	{
		if(find(i)!=find(i-1))
		{
			printf("-1");
			return 0;
		}
	}
	if(k<0)
	{
		printf("-1");
		return 0;
	}
//	for(int i=0;i<v1.size();i++)
//	{
//		if(!del[v1[i]])
//		{
//			printf("%d ",v1[i]);
//		}
//	}
	while(k--)
	{
		if(!v2.size())
		{
			printf("-1");
			return 0;
		}
		for(int i=1;i<=n;i++) mx[i]=0;
		for(int i=head[1];i;i=e[i].nxt) dfs(e[i].to,1);
		int now=-1,ans=2e9;
		for(int i=0;i<v2.size();i++)
		{
			int x=v2[i],tmp=max(a[x].u,a[x].v);
			if(a[x].w-e[mx[tmp]].val<ans)
			{
				ans=a[x].w-e[mx[tmp]].val,now=i;
			}
		}
		int x=v2[now],tmp=max(a[x].u,a[x].v);
		add_edge(a[x].u,a[x].v,a[x].w,a[x].id);
		add_edge(a[x].v,a[x].u,a[x].w,a[x].id);
		vis[mx[tmp]]=vis[mx[tmp]^1]=1;
		del[e[mx[tmp]].id]=1;
		v1.push_back(a[x].id);
		v2.erase(v2.begin()+now);
	}
	printf("%d\n",n-1);
	for(int i=0;i<v1.size();i++)
	{
		if(!del[v1[i]])
		{
			printf("%d ",v1[i]);
		}
	}
	return 0;
}

The Shortest Statement

https://www.gxyzoj.com/d/hzoj/p/4472

可以先在图中找一些边,是这些边形成一棵树

此时,如果u,v最短路在树边上,lca求解即可

如果经过非树边,因为mn<=20,至多只有42个点与非树边相连

此时,求出这些点到所有点的最短路,每次询问将到u和到v的距离相加再取min即可

点击查看代码
#include<cstdio>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,f[100005],edgenum,head[100005],id;
struct edge{
	int to,nxt,tag;
	ll val;
}e[200005];
void add_edge(int u,int v,int w,int tag)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].tag=tag;
	e[edgenum].val=w;
	e[edgenum].to=v;
	head[u]=edgenum;
}
int find(int x)
{
	if(f[x]!=x) f[x]=find(f[x]);
	return f[x];
}
queue<int> q;
ll dis[43][100005];
bool vis[100005],inq[100005];
void spfa(int s,int x)
{
	q.push(s);
	dis[x][s]=0;
	while(!q.empty())
	{
		int u=q.front();
//		printf("%d\n",u);
		q.pop();
		inq[u]=0;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
//			printf("%d %d %d %d\n",u,v,dis[x][v],dis[x][u]);
			if(dis[x][v]>dis[x][u]+e[i].val)
			{
				dis[x][v]=dis[x][u]+e[i].val;
				if(!inq[v])
				{
					q.push(v);
					inq[v]=1;
				}
			}
		}
	}
}
int f1[100005][20],dep[100005];
ll d[100005][20];
void dfs(int u,int fa)
{
	dep[u]=dep[fa]+1,f1[u][0]=fa;
	for(int i=1;i<=17;i++)
	{
		f1[u][i]=f1[f1[u][i-1]][i-1];
		d[u][i]=d[u][i-1]+d[f1[u][i-1]][i-1];
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(e[i].tag||fa==v) continue;
		d[v][0]=e[i].val;
		dfs(v,u);
	}
}
ll lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	ll res=0;
	for(int i=17;i>=0;i--)
	{
		if(dep[f1[x][i]]>=dep[y])
		{
			res+=d[x][i];
			x=f1[x][i];
		}
		if(x==y) return res;
	}
	for(int i=17;i>=0;i--)
	{
		if(f1[x][i]!=f1[y][i])
		{
			res+=d[x][i]+d[y][i];
			x=f1[x][i],y=f1[y][i];
		}
	}
	res+=d[x][0]+d[y][0];
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		f[i]=i;
	}
	for(int i=1;i<=m;i++)
	{
		int u,v,fl=1,w;
		scanf("%d%d%d",&u,&v,&w);
		if(find(u)!=find(v))
		{
			f[find(u)]=find(v);
			fl=0;
		}
		else vis[u]=vis[v]=1;
		add_edge(u,v,w,fl);
		add_edge(v,u,w,fl);
	}
//	printf("1");
	for(int i=1;i<=n;i++)
	{
		if(vis[i])
		{
//			printf("%d ",i);
			id++;
			for(int j=1;j<=n;j++)
			{
				dis[id][j]=1e18;
			}
			spfa(i,id);
		}
	}
	dfs(1,0);
	int q;
	scanf("%d",&q);
	while(q--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		ll ans=lca(x,y);
		for(int i=1;i<=id;i++)
		{
			ans=min(ans,dis[i][x]+dis[i][y]);
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @   wangsiqi2010916  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示