Codeforces Round /#580 (Div. 2) D. Shortest Cycle(Floyd求最小环)


题目地址

https://codeforces.com/contest/1206/problem/D

题目大意

给n个数字(n是1e5级),如果a[i]&a[j]!=0,则i点到j点有连边,问最短的环长度是多少。

解法

先按位扫数组,如果一个位在数字上出现的次数超过了三次,那么这三个数字可以直接成环,输出答案3.

接下来讨论每个位最多两个数字的情况,这种情况最多只有2*64个数字,因此算法的复杂度可以比较大。但要记得筛除a[i]=0的情况。

那么floyd是怎么求最小环的呢?

rep(i,1,n)
    rep(j,1,n)
        mp[i][j]=e[i][j];//把一开始的e复制一遍给mp
rep(k,1,n){
    //当循环到k的时候,可以保证之前的e[i][j]路径中都不会包含k
    rep(i,1,k-1){
        rep(j,i+1,k-1){
            minn = min(minn,e[i][j]+mp[i][k]+mp[j][k]);//i->j->k->i这个环长度
            //这句后面两个一定要是mp!一开始错在这里
            //想明白了是因为圈上会有边被经过两次,类似丫字型,原本不能成环,但如果不是mp会被误判成环
        }
    }
    rep(i,1,n){
        rep(j,1,n){
            e[i][j]=min(e[i][j],e[i][k]+e[k][j]);//这样处理之后,路径可以包含k
        }
    }
}

可以比较直观地看到,第一层枚举k,内层的i和j都不会大于k,这样就保证了新加入的点k不在任何的eij路径中。那么这样一来,i->j是i到j的一条路径,i->k->j是第二条路径,这样一来就是一个环。在这些解里面找最小值即可。

!之前比较疑惑的点就是,为什么要保留最初的邻接矩阵e在mp中呢?

因为如果都使用数组e,即minn = min(minn,e[i][j]+mp[i][k]+mp[j][k]),在这种情况下会出错:

tu1

显然原本是不能成环的,但是$e_{i,k}$和$e_{j,k}$都有值,这样在minn = min(minn,e[i][j]+mp[i][k]+mp[j][k])下是有解的,实际上这条路径用了重复的路,所以没环,就出错了。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=(b);i--)
#define INF 0x3f3f3f3f

const int MAXN = 114514;
ll a[MAXN];
int e[666][666],mp[666][666];
int n;
vector<ll> vec;
void solve(){
    cin>>n;
    rep(i,1,n) cin>>a[i];
    ll tmp;
    rep(i,0,63){
        tmp = 1ll<<i;
        int cnt = 0;
        rep(i,1,n)
            if(a[i]&tmp) cnt++;
        if(cnt>=3){
            cout<<3<<endl;
            return;
        }
    }
    vec.push_back(-1);//填个空
    rep(i,1,n)
        if(a[i]!=0) vec.push_back(a[i]);//防止0的情况
    n = vec.size()-1;
    //最多2*64个数,floyd找最小环
    int minn = INF;
    rep(i,1,n){
        rep(j,1,n){
            e[i][j] = INF/3;//INF的话会越界
            if(i==j) e[i][j]=0;
            else if(vec[i]&vec[j]) e[i][j]=1;
        }
    }
    //floyd
    rep(i,1,n)
        rep(j,1,n)
            mp[i][j]=e[i][j];//把一开始的e复制一遍给mp
    rep(k,1,n){
        //当循环到k的时候,可以保证之前的e[i][j]路径中都不会包含k
        rep(i,1,k-1){
            rep(j,i+1,k-1){
                minn = min(minn,e[i][j]+mp[i][k]+mp[j][k]);//i->j->k->i这个环长度
                //这句后面两个一定要是mp!一开始错在这里
                //想明白了是因为圈上会有边被经过两次,类似丫字型,原本不能成环,但如果不是mp会被误判成环
            }
        }
        rep(i,1,n){
            rep(j,1,n){
                e[i][j]=min(e[i][j],e[i][k]+e[k][j]);//这样处理之后,路径可以包含k
            }
        }
    }
    if(minn>n) cout<<-1<<endl;
    else cout<<minn<<endl;
}

int main(){
    solve();
}

文章作者: REXWind
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 REXWind !
评论
  目录