图论错题集2

这篇博客汇总了几个涉及图论的算法问题,包括铜牌题的挑战。作者探讨了如何通过最小生成树解决无向图问题,利用kruscal重构树处理路径最大值查询,以及如何在有环的图中进行有效的路径优化。此外,还讨论了如何在基环树中计算路径条数,构建最小生成最短路图,以及解决最小位或生成树问题。博客中还提到了孤岛营救问题的状压BFS解决方案,以及一个涉及井跳的搜索优化问题。
摘要由CSDN通过智能技术生成

Problem - H - Codeforces

铜牌题

题意:给你一张无向图,点权代表你到达这个点时能获得的能力值,边权代表你当前必须拥有大于等于该边权的能力值你才能经过这条边,现在有q次询问,问每次从x位置以初始能力值y出发最多能获得多少能力值(可以反复经过点和边,但是点上的能力值只能获得一次)

思路:首先要考虑到如果某些边构成了一个环,那么这个环中最大的那条边是没有用处的,所以我们应该转化成一颗最小生成树去求解(vp的时候就到此为止了,呜呜呜),然后涉及到一个知识点:kruscal重构树,不知道是啥的可以百度或者看我的图论错题集1的最后一题,利用这个生成树的性质我们可以很容易维护出任意两点之间路径上最大边权的最小值,这样对于给定起点和终点的询问我们就可以O(1)的处理,但是这题要求的是最多能获得多少的能力值,换句话说就是在这棵树上它最多能跳到第几层,曾越高获得的能力值就越多,但是朴素暴力的复杂度会到O(n)(每次向父亲跳),怎么简化跳跃次数呢?倍增!这可是个好东西,不知道倍增的可以去学一学树上倍增解lca,其实就是我们利用二进制拆分维护出每个点的第1,2,4,8......轮祖先,然后每次只需要logn的次数就可以跳到我们需要的那个点(前提是你能找到一种单调性),这样对于每次询问我们只需要logn即可找到答案。但是有一个问题,我看了好多题解,写的都和我一样,但是这种写法如果说我每次正好只能跳到直接父亲,并且每次都是这样一直跳到根的话,它不会退化到暴力求解的复杂度吗(存疑)

 #include <bits/stdc++.h>
 using namespace std;
 #define visit _visit
 #define next _next
 #define pb push_back
 #define fi first
 #define se second
 #define endl '\n'
 #define fast ios::sync_with_stdio(0), cin.tie(0)
 #define int long long
 #define ll long long
 #define pint pair<int,int>
 ​
 const int mod = 998244353;
 const int maxn = 200001;
 const int INF = 0x3f3f3f3f;
 ​
 void read(int &x){
     int f=1;x=0;char s=getchar();
     while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
     while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
     x*=f;
 }
 ll quick_pow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
 ll inv(ll x) {return quick_pow(x, mod-2);}
 //----------------------------------------------------------------------------------------------------------------------//
 int val[maxn<<1];
 int w[maxn<<1]; 
 struct e{
     int a,b,val;
 };
 e es[maxn];
 bool com(e a,e b){
     return a.val<b.val;
 }
 int fa[maxn<<1];
 int find(int x){
     if(fa[x]==x){
         return x;
     }
     return fa[x]=find(fa[x]);
 }
 void join(int a,int b){
     int fa1=find(a);
     int fa2=find(b);
     fa[fa1]=fa2;
 }
 struct node{
     int ne,to;
 };
 node edge[maxn<<2];
 int head[maxn<<1];
 int cnt=0;
 void addedge(int a,int b){
     edge[cnt].to=b;
     edge[cnt].ne=head[a];
     head[a]=cnt++;
 }
 int fa_st[maxn][21];
 void dfs(int x,int f){
     fa_st[x][0]=f;
     for(int i=1;i<=20;i++){
         fa_st[x][i]=fa_st[fa_st[x][i-1]][i-1];
     }
     for(int i=head[x];i!=-1;i=edge[i].ne){
         int son=edge[i].to;
         if(son==f){
             continue;
         }
         dfs(son,x);
     }
 }
 ​
 int query(int s,int v){
     int ans=val[s]+v;
     while(1){
         int tem=s;
         for(int i=20;i>=0;i--){
             int f=fa_st[s][i];
             if(ans>=w[f]){
                 s=fa_st[s][i];
             }
             ans=val[s]+v;           
         }
         if(fa_st[s][0]==0||tem==s){
             break;
         }
     }
     return ans;
 }
 void solve(){
     int n,m,q;
     cin>>n>>m>>q;
     for(int i=0;i<=n*2;i++){
         fa[i]=i;
         head[i]=-1;
     }
     for(int i=1;i<=n;i++){
         cin>>val[i];
     }
     for(int i=1;i<=m;i++){
         cin>>es[i].a>>es[i].b>>es[i].val;
     }
     sort(es+1,es+1+m,com);
     int num=n;
     for(int i=1;i<=m;i++){
         int a=es[i].a;
         int b=es[i].b;
         int fa1=find(a);
         int fa2=find(b);
         if(fa1==fa2){
             continue;
         }
         num++;
         join(fa1,num);
         join(fa2,num);
         w[num]=es[i].val;
         val[num]=val[fa1]+val[fa2];
         addedge(fa1,num);
         addedge(num,fa1);
         addedge(fa2,num);
         addedge(num,fa2);
     }
     dfs(num,0);
     w[0]=INF*INF;
     while(q--){
         int s,v;
         cin>>s>>v;
         cout<<query(s,v)<<endl;
     }
 }
 ​
 signed main(){
     fast;
     int t=1;
     //cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

登录—专业IT笔试面试备考平台_牛客网

铜牌题

题意:给你一个全排列,你每轮可以选择任意对数将其交换(每轮中一个数只能用一次),然后求最少的轮数将所有的数都放到应该在的位置,即变成1-n

思路:首先要能想到如果以图论的角度思考的话,这张代表位置关系的图其实都是由环组成的,而环无非奇环和偶环两种,最容易想到的做法就是我每次去把当前位置需要的数换过来,这样一次换出1/2,最多只需要logn轮就可以换完,但是还有更简单的做法,我们考虑让第一轮为第二轮做铺垫,即在第一轮时将所有的环都拆成长度为2的偶环,那能不能做到呢,我们举例来看,假设我这张图上点表示的是位置,u-v的边代表u这个位置上的数需要移动到v这个位置上,那么我的交换操作在这张图上就体现为交换结点,并且入边不变,出边随点变化,所以对于奇环来说我们只需要将1~n/2分别和n-1~n/2+1交换即可全部构造成长度为2的偶环。

 #include <bits/stdc++.h>
 using namespace std;
 #define visit _visit
 #define next _next
 #define pb push_back
 #define fi first
 #define se second
 #define endl '\n'
 #define fast ios::sync_with_stdio(0), cin.tie(0)
 #define int long long
 #define ll long long
 #define pint pair<int,int>
 ​
 const int mod = 998244353;
 const int maxn = 200001;
 const int INF = 0x3f3f3f3f;
 ​
 void read(int &x){
     int f=1;x=0;char s=getchar();
     while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
     while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
     x*=f;
 }
 ll quick_pow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
 ll inv(ll x) {return quick_pow(x, mod-2);}
 //------------------
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuhudaduizhang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值