图论错题集2

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);}
 //----------------------------------------------------------------------------------------------------------------------//
 int x[maxn];
 vector<int>loop;
 vector<pint>ans[2];
 void solve(){
     int n;
     cin>>n;
     for(int i=1;i<=n;i++){
         cin>>x[i];
     }
     for(int i=1;i<=n;i++){
         if(x[i]==i){
             continue;
         }
         loop.clear();
         int now=i;
         loop.push_back(i);
         now=x[now];
         while(now!=i){
             loop.push_back(now);
             now=x[now];
         }
         if(loop.size()==2){
             ans[0].push_back({loop[0],loop[1]});
             swap(x[loop[0]],x[loop[1]]);
         }else{
             int l=1,r=loop.size()-1;
             while(l<r){
                 ans[0].push_back({loop[l],loop[r]});
                 swap(x[loop[l]],x[loop[r]]);
                 swap(loop[l],loop[r]);
                 l++;
                 r--;
             }
             l=0,r=loop.size()-1;
             while(l<r){
                 ans[1].push_back({loop[l],loop[r]});
                 swap(x[loop[l]],x[loop[r]]);
                 l++;
                 r--;
             }
         }
     }
     if(ans[0].size()==0){
         cout<<0<<endl;
         return;
     }
     if(ans[1].size()==0){
         cout<<1<<endl;
         cout<<ans[0].size()<<" ";
         for(auto i :ans[0]){
             cout<<i.fi<<" "<<i.se<<" ";
         }
         cout<<endl;
     }else{
         cout<<2<<endl;
         for(int k=0;k<2;k++){
             cout<<ans[k].size()<<" ";
             for(auto i :ans[k]){
                 cout<<i.fi<<" "<<i.se<<" ";
             }
             cout<<endl;
         }
     }
 }
 ​
 signed main(){
     fast;
     int t=1;
     //cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

可恶啊,vp三把都只有三题,都铁了,唯二两道铜牌图论,一个是kruscal重构树,一个是这个构造,我都没写出来,tnnd

Problem - E - Codeforces

题意:给你一个基环树(一个包含一个简单环的树),求所有大于一的路径条数

思路:肯定和组合数有关,我们考虑经过了环的那些点对,这些点对一定可以通过这个环找到另外一条路径,所以价值是2,而那些没有经过环的一定只会有1的贡献,因为他们在树上,但是这样计算比较麻烦,所以我们反过来思考,我们先假设每条边的价值都是2,那么总路径条数就是n*(n-1),然后再去计算那些价值为1的点对的数量,如果我们把这个环删除,那么图上剩下来的都是一颗颗树,而这每棵树上所有的点对就是我们想要求的价值为1的点对,用并查集分别计算他们的数量即可。(话说我最近才意识到拓扑排序也可以用于有向图,被自己菜笑了)

 #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);}
 //----------------------------------------------------------------------------------------------------------------------//
 struct node{
     int ne,to;
 };
 node edge[maxn<<1];
 int head[maxn];
 int cnt=0;
 void addedge(int a,int b){
     edge[cnt].to=b;
     edge[cnt].ne=head[a];
     head[a]=cnt++;
 }
 int du[maxn];
 int n;
 int vis[maxn];
 void tuopu(){
     queue<int>q;
     for(int i=1;i<=n;i++){
         if(du[i]==1){
             q.push(i);
         }
     }
     while(!q.empty()){
         int t=q.front();
         q.pop();
         vis[t]=1;
         for(int i=head[t];i!=-1;i=edge[i].ne){
             int son=edge[i].to;
             du[son]--;
             if(du[son]==1){
                 q.push(son);
             }
         }
     }
 }
 int num[maxn];
 int fa[maxn];
 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;
     return;
 }
 void solve(){
     cin>>n;
     for(int i=0;i<=n;i++){
         head[i]=-1;
         vis[i]=0;
         fa[i]=i;
         du[i]=0;
         num[i]=0;
     }
     for(int i=1;i<=n;i++){
         int a,b;
         cin>>a>>b;
         addedge(a,b);
         addedge(b,a);
         du[a]++;
         du[b]++;
     }
     tuopu();
     for(int i=1;i<=n;i++){
         for(int j=head[i];j!=-1;j=edge[j].ne){
             int son=edge[j].to;
             if(vis[son]==0&&vis[i]==0){
                 continue;
             }
             join(son,i);
         }
     }
     int ans=n*(n-1);
     
     for(int i=1;i<=n;i++){
         num[find(i)]++;
     }
     for(int i=1;i<=n;i++){
         ans-=num[i]*(num[i]-1)/2;
     }
     cout<<ans<<endl;
 }
 ​
 signed main(){
     fast;
     int t=1;
     cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

Problem - E - Codeforces

题意:一张图,给定起点,求这张图的最小生成最短路图,即这张新图包含原图所有的点,并且使得所有的点到给定起点的最短路径长度不变

思路:我本来想的是先跑一遍dij然后筛选出所有可能成为最短路的边,然后再在这张新图上跑最小生成树,但是这样就有可能筛掉那些必须被选保证到某点最短路不变长的边,因此正确的做法应该是我们在dij的过程中选择那些到达这个点的时候最后一条边最短的路径,这些路径上的点即构成了我们要求的最小生成最短路图。

 #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 = 300001;
 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);}
 //----------------------------------------------------------------------------------------------------------------------//
 struct node{
     int ne,to,val,idx;
 };
 node edge[maxn<<1];
 int head[maxn];
 int cnt=0;
 void addedge(int a,int b,int val,int idx){
     edge[cnt].to=b;
     edge[cnt].ne=head[a];
     edge[cnt].val=val;
     edge[cnt].idx=idx;
     head[a]=cnt++;
 }
 int pre[maxn];
 int mn_last[maxn];
 int dis[maxn],vis[maxn];
 void dij(int s){
     priority_queue<pint>q;
     q.push({0,s});
     dis[s]=0;
     while(!q.empty()){
         int t=q.top().se;
         q.pop();
         if(vis[t]==1){
             continue;
         }
         vis[t]=1;
         for(int i=head[t];i!=-1;i=edge[i].ne){
             int son=edge[i].to;
             if(dis[son]>dis[t]+edge[i].val){
                 pre[son]=t;
                 dis[son]=dis[t]+edge[i].val;
                 q.push({-dis[son],son});
                 mn_last[son]=edge[i].val;
             }else if(dis[son]==dis[t]+edge[i].val){
                 if(edge[i].val<mn_last[son]){
                     pre[son]=t;
                     dis[son]=dis[t]+edge[i].val;
                     q.push({-dis[son],son});
                     mn_last[son]=edge[i].val;
                 }
             }
         }
     }
 }
 struct es{
     int a,b,val,idx;
 };
 es e[maxn];
 bool com(es a,es b){
     return a.val<b.val;
 }
 int fa[maxn];
 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;
     return;
 }
 map<pint,pint>mp;
 map<pint,int>tes;
 ​
 void solve(){
     memset(head,-1,sizeof(head));
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++){
         fa[i]=i;
     }
     for(int i=1;i<=m;i++){
         cin>>e[i].a>>e[i].b>>e[i].val;
         int a=e[i].a;
         int b=e[i].b;
         mp[{min(a,b),max(a,b)}]={i,e[i].val};
         e[i].idx=i;
         addedge(e[i].a,e[i].b,e[i].val,i);
         addedge(e[i].b,e[i].a,e[i].val,i);
     }
     memset(dis,0x3f,sizeof(dis));
     int s;
     cin>>s;
     dij(s);
     vector<int>ans;
     int sum=0;
     pre[s]=s;
     for(int i=1;i<=n;i++){
         int tem=i,la=i;
         while(tem!=s){
             la=tem;
             tem=pre[tem];
             int a=min(la,tem);
             int b=max(la,tem);
             if(tes[{a,b}]==0){
                 tes[{a,b}]=1;
                 ans.push_back(mp[{a,b}].fi);
                 sum+=mp[{a,b}].se;
             }else{
                 break;
             }
         }
     }
     cout<<sum<<endl;
     for(auto i :ans){
         cout<<i<<" ";
     }
     cout<<endl;
 }
 ​
 signed main(){
     fast;
     int t=1;
     //cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

评论区大佬的解释:

 

 

 

Problem - 1624G - Codeforces

题意:一张无向连通图,求其最小位或生成树,即生成树的边权的位或值最小。

思路:从高位向地位枚举,我们看能否把这一位改为0,我一开始想的是如果能改为0就说明所有当前这一位为1的边我们都不用也能使这张图联通,但是这样我们就会丢失掉我们之前筛选过的不能使用的边,因此我们需要在筛选当前轮可以使用的边时考虑到前面已经计算出的答案的影响,所以正确的做法是对于当前轮如果一条边的权值和当前的ans的位或值为ans,那么就说明当前这条边满足我们之前所推导出的关于ans的每一位的情况,那么就可以在本轮判断联通的过程中使用。

太妙了呜呜呜,想了半天还是得看zy大人的代码,其实也有不专心的锅,在看jls和dls打mirror(。

 #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);}
 //----------------------------------------------------------------------------------------------------------------------//
 struct node{
     int ne,to,val,flag;
 };
 node edge[maxn<<1];
 int head[maxn];
 int cnt=0;
 void addedge(int a,int b,int val){
     edge[cnt].to=b;
     edge[cnt].val=val;
     edge[cnt].ne=head[a];
     edge[cnt].flag=0;
     head[a]=cnt++;
 }
 int ans=1;
 int vis[maxn];
 void dfs(int x){
     vis[x]=1;
     for(int i=head[x];i!=-1;i=edge[i].ne){
         int son=edge[i].to;
         if(edge[i].flag==0){
             continue;
         }
         if(vis[son]==1){
             continue;
         }
         dfs(son);
     }
 }
 void solve(){
     int n,m;
     cin>>n>>m;
     cnt=0;
     for(int i=0;i<=n;i++){
         head[i]=-1;
     }
     for(int i=1;i<=m;i++){
         int a,b,val;
         cin>>a>>b>>val;
         addedge(a,b,val);
         addedge(b,a,val);
     }
     ans=1;
     for(int i=1;i<=30;i++){
         ans*=2;
         ans+=1;
     }
     int tem=1;
     for(int i=30;i>=0;i--){
         ans-=(1<<i); 
         for(int i=1;i<=n;i++){
             vis[i]=0;
         }
         for(int j=0;j<cnt;j++){
             edge[j].flag=0;
         }
         for(int j=0;j<cnt;j++){
             if((edge[j].val|ans)==ans){
                 edge[j].flag=1;
             }
         }
         dfs(1);
         int flag=1;
         for(int i=1;i<=n;i++){
             if(vis[i]==0){
                 flag=0;
                 break;
             }
         }
         if(flag==0){
             ans+=(1<<(i));
         }
     }
     cout<<ans<<endl;
 }
 ​
 signed main(){
     fast;
     int t=1;
     cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

孤岛营救问题 - 洛谷

题意:给你一张图,图上有好多种类的门,并且有许多不同种类的钥匙,一种类型的门只能用对应种类的钥匙打开,问你从 (1,1)到(n,m)的最短路径。

思路:《网络流24题》,但是其实是个状压bfs,因为钥匙的数量很少,所以我们考虑对当前拥有的钥匙进行状压,这样每个位置都只会有2^9种情况,总复杂度就是nm*2^9。

 #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);}
 //----------------------------------------------------------------------------------------------------------------------//
 struct node{
     int x,y,val;
 };
 int ne[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
 int mp[11][11][11][11];
 int vis[11][11][520];
 int dis[11][11][520];
 int key[11][11];
 int n,m,p;
 void bfs(){
     queue<node>q;
     q.push({1,1,0});
     vis[1][1][0]=1;
     dis[1][1][0]=0;
     while(!q.empty()){  
         int x=q.front().x;
         int y=q.front().y;
         int v=q.front().val;
         int tem=v;
         q.pop();
         if(key[x][y]!=0){
             v|=key[x][y];
             vis[x][y][v]=1;
         }
         //cout<<x<<" "<<y<<" "<<v<<endl;
         for(int i=0;i<4;i++){
             int a=x+ne[i][0];
             int b=y+ne[i][1];
             if(a<1||b<1||a>n||b>m||vis[a][b][v]==1||mp[x][y][a][b]==0){
                 continue;
             }
             if(mp[x][y][a][b]==-1){
                 q.push({a,b,v});
                 dis[a][b][v]=dis[x][y][tem]+1;
                 vis[a][b][v]=1;
             }else if(((1<<(mp[x][y][a][b]-1))&v)!=0){
                 //cout<<a<<" "<<b<<" "<<v<<endl;
                 q.push({a,b,v});
                 dis[a][b][v]=dis[x][y][tem]+1;
                 vis[a][b][v]=1;
             }
         }
     }   
 }
 void solve(){
     memset(mp,-1,sizeof(mp));
     memset(dis,-1,sizeof(dis));
     cin>>n>>m>>p;
     int k;
     cin>>k;
     for(int i=1;i<=k;i++){
         int x1,y1,x2,y2,g;
         cin>>x1>>y1>>x2>>y2>>g;
         mp[x1][y1][x2][y2]=g;
         mp[x2][y2][x1][y1]=g;
     }
     int q;
     cin>>q;
     while(q--){
         int a,b,v;
         cin>>a>>b>>v;
         key[a][b]|=(1<<(v-1));
     }
     bfs();
     for(int i=1;i<=n;i++){
         for(int j=1;j<=n;j++){
             
         }
     }
     int ans=INF;
     for(int i=0;i<520;i++){
         //cout<<dis[n][m][i]<<" ";
         if(dis[n][m][i]==-1){
             continue;
         }
         ans=min(ans,dis[n][m][i]);
     }
     if(ans==INF){
         cout<<-1<<endl;
     }else{
         cout<<ans<<endl;
     }
 }
 ​
 signed main(){
     fast;
     int t=1;
     //cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

Problem - B - Codeforces

题意:一口井,你在不同的深度可以跳最多ai米,到每个位置会下滑bi米,求最少几步跳出。

思路:换成只能跳ai米的话这就是个水题了,但是现在这样的话边太多了没法建图,那怎么办呢?俗话说的好,啥也不会先想搜索,我们注意到每个位置最多只需要访问一次,后面再访问花费一定比现在多,所以我们bfs的话按理说只需要搜n次,但是如果直接循环1-a[i]的话复杂度就会爆掉,然后这里就会有一个很牛逼的优化,我们用set维护所有未访问过的状态然后二分地去找1-a[i]这个范围内所有还没有访问过的结点,这样复杂度就会被优化到n*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 = 300001;
 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 a[maxn],b[maxn];
 set<int>vis;
 int ans[maxn];
 int pre[maxn];
 int idx[maxn];
 int n;
 void bfs(int s){
     queue<int>q;
     q.push(s);
     for(int i=0;i<=n+1;i++){
         vis.insert(i);
     }
     ans[s]=1;
     while(!q.empty()){
         int t=q.front();
         q.pop();
         while(1){
             auto x = *vis.lower_bound(t-a[t]);
             if(x>t){
                 break;
             }
             vis.erase(x);
             int son=x+b[x];
             if(ans[son]>ans[t]+1||ans[son]==0){
                 ans[son]=ans[t]+1;
                 pre[son]=t;
                 idx[son]=x;
                 q.push(son);
             }
             
         }
     }
 }
 void solve(){
     
     cin>>n;
     for(int i=1;i<=n;i++){
         cin>>a[i];
     }
     for(int i=1;i<=n;i++){
         cin>>b[i];
     }
     bfs(n); 
     if(ans[0]==0){
         cout<<-1<<endl;
         return;
     }
     cout<<ans[0]-1<<endl;
     int t=0;
     vector<int>path;
     while(t!=n){
         path.pb(idx[t]);
         t=pre[t];
     }
     reverse(path.begin(),path.end());
     for(auto i :path){
         cout<<i<<" ";
     }   
     cout<<endl;
     
 }
 ​
 signed main(){
     fast;
     int t=1;
     //cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

Problem - E - Codeforces

题意:一张无向图图,求1-n的最短路径,但是不能按顺序经过给定的一些点的三元组比如给你134那么你这条路径中就不能连续先后经过1-3-4。

思路:仔细想一想这个和普通的图有什么不一样?由于这样的三元组的存在,我们在每一个位置下一步的决策和我上一步和上上步的状态有关系,换句话说,只要我上一步和上上步一样,那这两个状态就一样,因此我们只需要在bfs的时候把每个点的状态用一个pair记录下上一步在哪就可以了。我愿称之为二维状态bfs(。

 #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 = 3001;
 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);}
 //----------------------------------------------------------------------------------------------------------------------//
 struct node{
     int ne,to,val;
 };
 node edge[400001];
 int head[3001];
 int cnt=0;
 void addedge(int a,int b,int v){
     edge[cnt].to=b;
     edge[cnt].ne=head[a];
     head[a]=cnt++;
 }
 map<pint,set<int> >tes;
 int vis[3001][3001];
 int pre[3001][3001];
 int dis[3001][3001];
 void solve(){
     int n,m,k;
     cin>>n>>m>>k;
     memset(head,-1,sizeof(head));
     for(int i=1;i<=m;i++){
         int a,b;
         cin>>a>>b;
         addedge(a,b,1);
         addedge(b,a,1);
     }
     for(int i=1;i<=k;i++){
         int a,b,c;
         cin>>a>>b>>c;
         tes[{a,b}].insert(c);
     }
     
     memset(dis,0x3f,sizeof(dis));
     queue<pint>q;
     q.push({0,1});
     vis[0][1]=1;
     dis[0][1]=0;
     int now=-1;
     while(!q.empty()){
         pint t=q.front();
         q.pop();
         if(t.se==n){
             now=t.fi;
             break;
         }
         for(int i=head[t.se];i!=-1;i=edge[i].ne){
             int son=edge[i].to;
             if(vis[t.se][son]==1||tes[{t.fi,t.se}].count(son)!=0){
                 continue;
             }
             if(dis[t.se][son]>dis[t.fi][t.se]+1){
                 dis[t.se][son]=dis[t.fi][t.se]+1;
                 vis[t.se][son]=1;
                 q.push({t.se,son});
                 pre[t.se][son]=t.fi;
             }
         }
     }
     if(now==-1){
         cout<<now<<endl;
         return;
     }
     cout<<dis[now][n]<<endl;
     vector<int>path;
     int ne=n;
     path.pb(n);
     while(1){
         path.pb(now);
         if(now==1){
             break;
         }
         int tmp=now;
         now=pre[now][ne];
         ne=tmp;
     }
     reverse(path.begin(),path.end());
     for(auto i:path){
         cout<<i<<" ";
     }
     cout<<endl;
 }
 ​
 signed main(){
     fast;
     int t=1;
     //cin>>t;
     while(t--){
         solve();
     }
     return 0;
 }

昆明铁了,查60罚时,难受,全是概率,期望,那道签到图论我题目都没读。。被歪的离谱的榜带着去看G了。麻了,大二唯一的一场就这么铁了,暑假加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wuhudaduizhang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值