图论相关问题
图论相关问题
A. P2662 牛场围栏
同余最短路的板题,会了就没什么。见这篇。
B. P4211 [LNOI2014] LCA
感觉离线处理的技巧得多加磨练。
这题的暴力显然就是
那么正解要么就是一次求出多个 LCA,要么就是把
前者不太可做,后者还是可以想想的。
考虑将
区间加、区间查,不是树剖还能是什么?不过我们不能直接暴力区间加和区间查,那样复杂度还是
但是这样貌似无法做到在线维护了,考虑离线。把一次询问拆成一个
主要难点在于转化,以及想到差分。能把这种问题差分以及离线处理的思想是需要练习的。
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 跳跳棋
神仙建模题,充分彰显了人类智慧。
设三点从小到大为
向两边跳 或 向中间跳
前者可以无限跳,但后者只能跳有限次数!因为一旦
反之,由
试着推广我们这个构想,对于任何一种状态,都能导出无限的状态,设其中两种分别为
有没有一种茅厕顿开的感觉!
别急,我们一步一步走。现在我们已经成功地将其转化为一个树上问题,还有几个问题亟待解决。首先,思考如何判断两个点之间不可达。其实是容易的,我们只要找到两个状态的初始状态,如果它们的初始状态不同,那么他俩就是不可达的。
但是又引出另外一个问题:如何找初始状态?值域在
到这里,我们应该就能很快地判断出两个状态可不可达。接下来的任务是找出两个可达的点的最小步数。
显然,我们应该找两点的 LCA。但是显然什么倍增树剖都没法用,注意到刚才我们找初始状态的那个过程是可以限制步数的,所以我们应该可以通过找每一个状态向上跳父亲的次数来找 LCA,因为找到 LCA 之后再往上跳必定是相同节点,具有单调性。所以二分步数即可。注意在此之前应当先将
捋一下思路:先找到 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
在几乎摸到正解的时候看了题解,他妈的。
最短路是肯定的,考虑找到最优路径。显然我们找到的最短路好边越多越好,在题解区看到了一种简洁的证明方法:
受影响的道路等于最短路上的坏边数和不在最短路上的好边数。设最短路上有
条好边和 条坏边,一共有 条好边,则受影响的道路总数为 条。又因为最短路上的坏边数等于 ,其中 是最短路长度,所以最终受影响的边数就是 条。
固定, 也固定,变量只有 。那么显然 越大越好。
于是我们在最短路的时候以路径长度为第一关键字、好边数为第二关键字进行比较即可。需要用一个
挺简单的。
G. CF125E MST Company
感觉如果用破圈的话这题的标签应该有
首先去掉
然后加入
然后不断地尝试把与
重复上面的过程,直到最终得到一棵强制
代码实现的主要难点在于如何找到加边所产生的环。其实仔细想想不难,因为我们已经得到了一棵树,所以我们可以依次遍历
H. CF1707C DFS Trees
诈骗题。
题目的切入点首先应该是由边权各不相同推出 MST 唯一,处理出 MST 上的边。然后注意到这段伪代码:
if vis[v] = false
add edge (u, v) into the set (s)
dfs(v)
这三句的意思是,只要一个点没有走过,就会去走这个点,并把这条边加入最小生成树。这样的走法显然是盲目的,如果从一个点出发经过了不是 MST 上的边,那么这个伪代码求出的最小生成树就错了。
于是问题转化为,如何判定从一个点出发会不会走到非树边上。
容易发现的是,若一条非树边的两个端点为
于是考虑给这些不合法的点加上一个权值,最后统计没有权值的点的个数。可以用树剖或树上差分解决。
I. CF1051F The Shortest Statement
初看题目怀疑自己,一看题解想骂自己,调完代码想笑自己。
突破口就是题目所给的
- 只经过树边的;
- 经过至少一条非树边的。
前者直接在生成树上跑 LCA 就可以
是不是觉得很简单,你可能十分钟就把生成树板子 + LCA 板子 + Dijkstra 板子码完了,但是这题有意思的地方才刚刚开始。下文仅是个人用 2.5h 调代码过程中的一些心得。
-
首先,处理生成树的时候,必须把
条边全部扫完,而不是加够了 条边就跳出。否则你会漏掉某些非树边并过不去第二个样例。 -
其次,我用
数组处理非树边端点,切记不要和 Dijkstra 的 数组搞混,尽管没有样例能测出来这个错误。 -
最后划重点,我把
个非树边的端点,每个点建立了一个结构体存储 数组和 函数,然后存到了一个 里面。但我最开始在每组询问遍历这个 的时候写的是:此时对奇技淫巧非常熟悉的同学可能就发现了华点:没错,这样写等于每次都要把这个结构体给
拷一遍,直接让你 TLE 到飞起。况且由于这样写等于在 main 函数内部开了一个 1e5 的数组,会直接 MLE!
正确的写法是:话说奇技淫巧好像是我写的 -
最后,链式前向星在这道题就是比邻接表快。
下课。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】