首页 > 其他分享 >图论相关问题

图论相关问题

时间:2025-01-22 17:12:08浏览次数:1  
标签:图论 初始状态 int text 问题 LCA 相关 非树边 节点

图论相关问题

A. P2662 牛场围栏

同余最短路的板题,会了就没什么。见这篇

B. P4211 [LNOI2014] LCA

感觉离线处理的技巧得多加磨练。

这题的暴力显然就是 \(O(nm)\) 或 \(O(nm\log n)\),暴力枚举 \([l,r]\) 和 \(z\) 的 LCA 的 \(\text{dep}\) 值,计算即可。

那么正解要么就是一次求出多个 LCA,要么就是把 \(\text{dep}\) 进行某种神奇的转化。

前者不太可做,后者还是可以想想的。

考虑将 \(\text{dep}_x\) 转化为 \(x\) 到根节点的节点个数。于是查询 \(\text{dep}_{\operatorname{LCA}(u,v)}\) 就可以转化为:将 \(u\) 到根节点的路径上的点权值 \(+1\),然后查询 \(v\) 到根节点的路径上的权值和。那么对于多个点也是同理的。

区间加、区间查,不是树剖还能是什么?不过我们不能直接暴力区间加和区间查,那样复杂度还是 \(O(nm\log n)\) 的。发现我们是要将 \([l,r]\) 的节点全部区间加,进而想到差分,也就是把 \(l-1\) 和 \(r\) 单拎出来,一个删一个加。

但是这样貌似无法做到在线维护了,考虑离线。把一次询问拆成一个 \([0,l-1]\) 和一个 \([0,r]\),全搞到一个结构体里面按照节点排序,然后扫一遍,用一个指针把区间加更新到当前点的位置,然后再判断当前点的种类,如果是要删的点就从答案中减去,否则加到答案上。

主要难点在于转化,以及想到差分。能把这种问题差分以及离线处理的思想是需要练习的。

struct Ask{
	int id,pos,z,k;
	bool operator<(const Ask&x)const{
		return pos<x.pos;
	}
}q[MAXN<<1];
ll ans[MAXN];

int main(){
	n=read(),m=read();
	for(int i=2,f;i<=n;++i){
		f=read()+1;
		addedge(f,i),addedge(i,f);
	}
	dfs(1,0);
	dfs2(1,1);
	for(int i=1,l,r,z;i<=m;++i){
		l=read()+1,r=read()+1,z=read()+1;
		q[(i<<1)-1]={i,l-1,z,-1};
		q[i<<1]={i,r,z,1};
	}
	sort(q+1,q+(m<<1|1));
	for(int i=1,j=1;i<=m<<1;++i){
		while(j<=q[i].pos) qadd(1,j++,1);
		if(~q[i].k) ans[q[i].id]+=qsum(1,q[i].z);
		else ans[q[i].id]-=qsum(1,q[i].z);
	}
	for(int i=1;i<=m;++i) write(ans[i]%MOD);
	return fw,0;
}

C. P1852 跳跳棋

神仙建模题,充分彰显了人类智慧

设三点从小到大为 \(x,y,z\),则不论怎么跳都有且只有两种情况:

  • \(y\) 向两边跳
  • \(x\) 或 \(z\) 向中间跳

前者可以无限跳,但后者只能跳有限次数!因为一旦 \(\text{dis}(x,y)=\text{dis}(y,z)\),那么 \(x\) 和 \(z\) 都不能再向中间跳了,否则不合法。

反之,由 \(\text{dis}(x,y)=\text{dis}(y,z)\) 这种状态可以导出无限多种状态,这不由得我们考虑将这个状态设为一棵树的根节点,那么别的状态都是它的子孙。为了方便,我们将这个状态定义为 “初始状态”。

试着推广我们这个构想,对于任何一种状态,都能导出无限的状态,设其中两种分别为 \(a,b\),那么从 \(a\) 变到 \(b\) 的最少步数是不是就是它们在这棵树上先变到它们的 LCA,再从 LCA 变到另外一种状态?

有没有一种茅厕顿开的感觉!

别急,我们一步一步走。现在我们已经成功地将其转化为一个树上问题,还有几个问题亟待解决。首先,思考如何判断两个点之间不可达。其实是容易的,我们只要找到两个状态的初始状态,如果它们的初始状态不同,那么他俩就是不可达的。

但是又引出另外一个问题:如何找初始状态?值域在 \(10^9\),显然是不能暴力搜的。不过我们可以试着想,就算这三个数是 \(1,2,10^9\) 这种变态,我们实际上也可以一步转移,因为把 \(x,y,z\) 这种状态翻转到 \(y,x,z\) 这种状态时,\(x,y\) 两点间距离不变,事实上从相对位置来看可以看作直接平移。所以刚才那个变态最终转移到 \(10^9-2,10^9-1,10^9\) 我们实际上一步就能算出是走了 \(\left\lfloor\dfrac{\text{dis}(y,z)-1}{\text{dis}(x,y)}\right\rfloor\) 步,\(-1\) 是为了防止最后出现两点重合的情况。至此我们找到初始状态的复杂度应该就是 \(\log\) 级别了。

到这里,我们应该就能很快地判断出两个状态可不可达。接下来的任务是找出两个可达的点的最小步数。

显然,我们应该找两点的 LCA。但是显然什么倍增树剖都没法用,注意到刚才我们找初始状态的那个过程是可以限制步数的,所以我们应该可以通过找每一个状态向上跳父亲的次数来找 LCA,因为找到 LCA 之后再往上跳必定是相同节点,具有单调性。所以二分步数即可。注意在此之前应当先将 \(a,b\) 中较深的一个跳到和另外一个同样深,然后再执行二分。最后的答案就是二分出的答案 \(\times2\) 再加上较深的那个点预先跳的步数。

捋一下思路:先找到 \(a,b\) 状态各自的初始状态,同时记录跳的步数。如果初始状态不同直接 NO;否则,将步数比较大的那一个先跳到和另外一个相同,然后二分找出两点的 LCA,最后答案如同上文统计即可。

#include<bits/stdc++.h>
using namespace std;

struct Node{
	int x,y,z;
	bool operator!=(const Node&b)const{
		return x!=b.x||y!=b.y||z!=b.z;
	}
	void read(){
		scanf("%d%d%d",&x,&y,&z);
		if(x>y) swap(x,y);
		if(x>z) swap(x,z);
		if(y>z) swap(y,z);
	}
}a,b;

Node getrt(Node t,int s,int&step){
	int k=0;
	for(step=0;s;step+=k){
		int d1=t.y-t.x,d2=t.z-t.y;
		if(d1==d2) return t;
		else if(d1>d2){
			k=min((d1-1)/d2,s);
			t.y-=k*d2,t.z-=k*d2;
			s-=k;
		}else{
			k=min((d2-1)/d1,s);
			t.x+=k*d1,t.y+=k*d1;
			s-=k;
		}
	}
	return t;
}

int main(){
	a.read(),b.read();
	int sa,sb,tmp; // a到根距离,b到根距离 
	Node rta=getrt(a,2e9,sa),rtb=getrt(b,2e9,sb);
	if(rta!=rtb) return puts("NO"),0;
	if(sa<sb) swap(sa,sb),swap(a,b);
	a=getrt(a,sa-sb,tmp);
	int l=0,r=sb,ans=l;
	while(l<=r){
		int mid=(l+r)>>1;
		if(getrt(a,mid,tmp)!=getrt(b,mid,tmp)) l=mid+1;
		else ans=mid<<1,r=mid-1;
	}
	printf("YES\n%d\n",ans+sa-sb);
	return 0;
}

E. CF507E Breaking Good

在几乎摸到正解的时候看了题解,他妈的。

最短路是肯定的,考虑找到最优路径。显然我们找到的最短路好边越多越好,在题解区看到了一种简洁的证明方法:

受影响的道路等于最短路上的坏边数和不在最短路上的好边数。设最短路上有 \(x\) 条好边和 \(y\) 条坏边,一共有 \(s\) 条好边,则受影响的道路总数为 \(s-x+y\) 条。又因为最短路上的坏边数等于 \(d-x\),其中 \(d\) 是最短路长度,所以最终受影响的边数就是 \(s-x+d-x=s+d-2x\) 条。

\(s\) 固定,\(d\) 也固定,变量只有 \(x\)。那么显然 \(x\) 越大越好。

于是我们在最短路的时候以路径长度为第一关键字、好边数为第二关键字进行比较即可。需要用一个 \(\text{pre}\) 数组记录路径,跑完最短路时重新扫一下路径,如果最短路上有坏边记录答案,好边可以用一个 set 维护,遇到好边就从 set 里擦掉。

挺简单的。

G. CF125E MST Company

感觉如果用破圈的话这题的标签应该有 \(\textbf{implementation}\),但貌似官方认可二分做法?

首先去掉 \(1\) 号点跑最小生成森林。设连出来的连通块个数为 \(x\),则若 \(k<x\),必然无解。

然后加入 \(1\) 号点向每个连通块连边权尽量小的边,这样就得到了一棵强制 \(x\) 度最小生成树。

然后不断地尝试把与 \(1\) 号点相连却没有进入最小生成树的边加入。每加入这样一条边,就需要删除由此所产生的环上的一条不与 \(1\) 相连的边。贪心地,我们应该删除边权最大的那一条。因为 \(n,k\le5000\),所以找这条边可以 \(O(n)\) 算,跑一遍 DFS 就行。

重复上面的过程,直到最终得到一棵强制 \(k\) 度最小生成树。

代码实现的主要难点在于如何找到加边所产生的环。其实仔细想想不难,因为我们已经得到了一棵树,所以我们可以依次遍历 \(1\) 节点的子树,计算出 \(\text{mx}(u)\) 表示 \(u\) 到 \(1\) 的路径上边权最大的边。向一棵子树多连一条边后,环肯定出现在这条边所连向的那个点到 \(1\) 节点的路径上,所以我们就找到了它。

H. CF1707C DFS Trees

诈骗题。

题目的切入点首先应该是由边权各不相同推出 MST 唯一,处理出 MST 上的边。然后注意到这段伪代码:

if vis[v] = false
    add edge (u, v) into the set (s)
    dfs(v)

这三句的意思是,只要一个点没有走过,就会去走这个点,并把这条边加入最小生成树。这样的走法显然是盲目的,如果从一个点出发经过了不是 MST 上的边,那么这个伪代码求出的最小生成树就错了。

于是问题转化为,如何判定从一个点出发会不会走到非树边上。

容易发现的是,若一条非树边的两个端点为 \(u,v\),设 \(\text{dep}(u)\ge\text{dep}(v)\),若 \(\operatorname{LCA}(u,v)\ne v\),则除了 \(u\) 和 \(v\) 的子树以外的点都不合法,因为它们只能从上面走到 \(u\) 和 \(v\) 中的一个,然后就会去走那条非树边。同理,若 \(\operatorname{LCA}(u,v)=v\),也就是说 \(v\) 是 \(u\) 的祖先,那么树上从 \(v\) 到 \(u\) 的链上的点为根的子树的点都不合法,因为这些点依然只能先走到 \(u\) 和 \(v\) 中的一个,然后通过非树边走另外一个。

\(u\) 和 \(v\) 本身是一定合法的,因为它们初始时就把自己走过了。

于是考虑给这些不合法的点加上一个权值,最后统计没有权值的点的个数。可以用树剖或树上差分解决。

I. CF1051F The Shortest Statement

初看题目怀疑自己,一看题解想骂自己,调完代码想笑自己。

突破口就是题目所给的 \(m-n\le20\)。让我们充分动用人类智慧,想到如果把这个图搞成一棵生成树,那么多出来的非树边顶多 21 条。再仔细想想,就可以把原图的最短路分成两种情况:

  • 只经过树边的;
  • 经过至少一条非树边的。

前者直接在生成树上跑 LCA 就可以 \(O(\log n)\) 地计算,后者只需以所有非树边的端点为原点跑 Dijkstra 就可以 \(O(42m\log m)\) 预处理 + \(O(42)\) 扫一遍查询。每一组询问对所有情况取 \(\min\) 即可。这题做完了。


是不是觉得很简单,你可能十分钟就把生成树板子 + LCA 板子 + Dijkstra 板子码完了,但是这题有意思的地方才刚刚开始。下文仅是个人用 2.5h 调代码过程中的一些心得。

  • 首先,处理生成树的时候,必须把 \(\boldsymbol m\) 条边全部扫完,而不是加够了 \(\boldsymbol{n-1}\) 条边就跳出。否则你会漏掉某些非树边并过不去第二个样例。

  • 其次,我用 \(\text{vis}\) 数组处理非树边端点,切记不要和 Dijkstra 的 \(\text{vis}\) 数组搞混,尽管没有样例能测出来这个错误。

  • 最后划重点,我把 \(42\) 个非树边的端点,每个点建立了一个结构体存储 \(\text{dis}\) 数组和 \(\text{Dijkstra}\) 函数,然后存到了一个 \(\tt vector\) 里面。但我最开始在每组询问遍历这个 \(\tt vector\) 的时候写的是:

    \[\texttt{for(auto x:V)} \]

    此时对奇技淫巧非常熟悉的同学可能就发现了华点:没错,这样写等于每次都要把这个结构体给 \(x\) 拷一遍,直接让你 TLE 到飞起。况且由于这样写等于在 main 函数内部开了一个 1e5 的数组,会直接 MLE!
    正确的写法是:

    \[\texttt{for(auto& x:V)} \]

    话说奇技淫巧好像是我写的

  • 最后,链式前向星在这道题就是比邻接表快。

下课。

标签:图论,初始状态,int,text,问题,LCA,相关,非树边,节点
From: https://www.cnblogs.com/laoshan-plus/p/18686448

相关文章

  • qt串口工具缓冲区大小引起的问题
    最近由于通过串口向下位机发送参数,需要将原来异步接收下位机数据的方式在参数下发时改成同步,为了解决由于参数反馈数据较小导致经常无法正常收到数据的问题,故将串口的缓冲区大小设置成了64。参数下发功能正常测试通过。后续。。。后面在使用过程中发现,当设置完参数后,若是有下位......
  • [lnsyoj2621/luoguP2756] 飞行员配对问题
    题意给定一侧\(n\)个点,一侧\(m-n\)个点的二分图,求最大匹配数及一个合法匹配sol二分图最大匹配问题。可以使用匈牙利算法或网络流解决,其中网络流通常更快。首先建立超级源点\(S\)和超级汇点\(T\),由于每个点只能与其他点匹配一次,原二分图中的每条边在网络流中容量应为......
  • 第二篇 Django的模版各功能示例 以及 初学者常见问题
    目录1、Django模版环境配置2、本项目遇到的问题:UsingtheURLconfdefinedinHelloWorld.urls,DjangotriedtheseURLpatterns,inthisorder:3、Django模版常用语法规则变量1、Django模版环境配置我们接着上一篇文章项目将在HelloWorld目录底下创建templat......
  • 记一次节点导致性能差的问题
    背景:一个应用部署了两个Pod,消费MQTT时发现PodA的CPU打满,PodB的CPU基本未使用。查看日志发现,PodA消费到的数据比PodB多5倍。猜测可能的结果如下:因PodB的消费能力不足,MQTT做了负载均衡(未收到消息ACK不会继续发送下一条消息)。将服务中的处理逻辑忽略,只做消费,立马返回ACK。验证结......
  • 06、Redis相关概念:缓存击穿、雪崩、穿透、预热、降级、一致性等
    Redis相关概念:缓存击穿、雪崩、穿透、预热、降级、一致性等Redis缓存雪崩、缓存击穿、缓存预热热点key、缓存降级、短链接、分布式锁秒杀、预减库存、堆外缓存+Redis架构设计、Redis动态刷新、Redis和DB双写一致性、过期删除策略、集群数据倾斜等一、缓存雪崩缓存......
  • Arch Linux - 中文乱码问题
    解决中文乱码问题,可以参考这这篇文章:Localization/SimplifiedChinese主要分成3个步骤locale配置中文字体不同软件的字体设置locale配置locale配置,其实是配置locale的环境变量LANGUAGELC_ALLLC_xxx,xxx表示不同的分类:CTYPE,TIME,...LANG可以执行命令locale查......
  • HTML表单相关知识
    表单的基本结构标签名标签语义常用属性单/双标签form表单action:用于指定表单的提交地址(需与后端人员沟通确定)method:用于控制表单的提交方式target:用于控制表单如何打开页面,常用值如下:_self:在本页签打开页面       _blank:在新页签打开页面双input输入框ty......
  • Csharp上传大文件到服务器指定文件夹问题
    功能:大文件上传下载,断点续传,文件夹上传下载,加密传输,加密存储,云对象存储要求:免费,开源,技术支持前端:vue2,vue3,react,vue-cli,html,jquery后端:asp.net,vb.net,.netcore,.netmvc,.netwebform平台:Windows,macOS,Linux,Ubuntu,RedHat,CentOS,中标麒麟,银河麒麟,统信UOS,信......
  • 如何迅速并识别处理MDL锁阻塞问题
    摘要:TaurusDB推出MDL锁视图功能,帮助用户迅速识别并处理MDL锁阻塞问题,从而有效减少对业务的负面影响,提升数据库管理效率。本文分享自华为云社区《【华为云MySQL技术专栏】TaurusDBMDL锁视图》,作者:GaussDB数据库。   一、背景数据库中的元数据锁(MDL,M......
  • OpenWRT24.10旁路由挂载USB移动硬盘,配置Samba4,作为NAS使用,解决中文不显示,乱码,解决断电
    1.为何选择OpenWRT24.10,及如何配置旁路由,或者IPv6地址看这篇:参OpenWRT24.10配置作为旁路由,并配置获取IPv4和IPv6地址使用的OpenWRT固件是从这里下载的:https://openwrt.ai/2.挂载大容量USB移动硬盘2.1安装必备插件kmod-fs-ntfs3kmod-fs-ext4kmod-fs-exfat#根据自己的......