【牛客练习赛67:最短路+位运算】E:牛妹游历城市

牛客练习赛67:E题 牛妹游历城市

【难度】

4.5 / 10 4.5/10 4.5/10
有点难度吧

【题意】

一共有 n n n 个点,每个点有权值 a i a_i ai
a i & a j ≠ 0 a_i\&a_j\ne0 ai&aj=0,则这两点之间连了一条双向边,边权为 l o w b i t ( a i & a j ) lowbit(a_i\&a_j) lowbit(ai&aj)

问你:从1号点到 n n n号的最短距离。
若无法到达输出“Impossible”

【数据范围】

1 ≤ n ≤ 1 0 5 1\le n\le10^5 1n105
1 ≤ a i ≤ 2 32 1\le a_i\le 2^{32} 1ai232

【样例输入】

T样例组数
n点数
a 1 ⋯ a n a_1\cdots a_n a1an

2
6
2 3 5 8 13 21
12
1 2 3 4 5 6 7 8 9 10 11 12

【样例输出】

3
5

【思路】

由于 n n n 十分大, n 2 n^2 n2枚举点对是否连通,再建图跑最短路是不行的:
时间复杂度大约为 O ( n 2 + 2 n l o g n ) O(n^2+2nlogn) O(n2+2nlogn)

因为点与点的连通条件为按位与非零,那么我们可以枚举二进制的每一位

为了后面解释方便,我暂时定义的定义:
第 i 个 团 为 G [ i ] G [ i ] 包 含 的 顶 点 为 : 二 进 制 从 右 到 左 第 i 位 为 1 的 所 有 点 。 \color{gray}第i个团为\color{green}G[i]\\ \color{green}G[i]包含的顶点为:二进制从右到左第i位为1的所有点。 iG[i]G[i]i1

那么易得,同一个团内的各个顶点之间都是相通的,也符合图论中团的定义。
这样虽然可以省去了枚举点对的建边的 O ( n 2 ) O(n^2) O(n2),但是每个团内的最短路仍然是 O ( n 2 ) O(n^2) O(n2)

因为点与点之间没有建边,我们只能通过 B f s \color{red}Bfs Bfs跑最短路。
因此每个点都要与团内的 O ( N ) O(N) O(N)个点进行访问、比较、张弛、更新,然后push进去。
这样时间复杂度显然为 O ( n 2 ) O(n^2) O(n2)

那么我们再考虑 【优化】

虽然每个顶点可以经过多次,但是由于边权一定是正数,所以经过一个点之后又回到了这个点显然没有必要。

然后考虑,对于团 G [ i ] G[i] G[i],团内的所有点的距离都是 2 i 2^i 2i
我们想跑 d i j k s t r a 优 化 dijkstra优化 dijkstra的话,需要从中每次选择距离最小的点进行更新。

那么这里,我们每次选择最小的团G[i]进行团内更新,并且每个团都只需要被更新一次

当然,我们还要更新出去。
比如再团0内的数字 10011 10011 10011,我们可以更新到团1和团4。

【核心代码】

时间复杂度:理论时间复杂度大约为 O ( N ) O(N) O(N),实际因为优先队列,会变为 O ( N log ⁡ N ) O(N\log N) O(NlogN)
Time(Ms)102 ms
Mem(Mb)10324 K

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/

ll aa[MAX];
vector<int>G[50];			/// 团
vector<int>Y[MAX];			/// 表示某个数的那些位为1
ll dis[MAX];				/// 表示 1点到该点的最短距离

ll lowbit(ll x){			/// lowbit
    return (-x) & x;
}

struct node{
    ll d;
    int tuan;
    int id;
    bool operator < (const node t)const{		/// 首先需要保证团最小。
        if(tuan != t.tuan)return tuan > t.tuan;
        return d > t.d;					/// 距离最小不需要保证,不过写了也可以
    }
};

priority_queue<node>Q;

int vis[50];						/// vis[i]表示第 i个团有没有访问过
void bfs(){
    while(!Q.empty()){
        ll d = Q.top().d;
        int T = Q.top().tuan;
        int x = Q.top().id;
        Q.pop();
        for(auto it : G[T]){
            ll w = lowbit(aa[x] & aa[it]);		/// 边权值
            if(dis[it] > w + d){			/// 张弛更新
                dis[it] = w + d;
                for(auto itt : Y[it])if(!vis[itt])Q.push({dis[it],itt,it}),vis[itt] = 1;			///更新出去
            }
        }
    }
}

int main()
{
    int T;
    T = read();
    while(T--){
        int n;n = read();
        for(int i=0;i<=40;++i)G[i].clear();		/// 初始化
        memset(vis,0,sizeof(vis));
        
        for(int i=1;i<=n;++i){
            Y[i].clear();
            aa[i] = read_ll();
            dis[i] = LINF;
            for(int j=0;j<=32;++j){
                if((1LL<<j) &aa[i])G[j].push_back(i),Y[i].push_back(j);
            }
        }
        dis[1] = 0LL;
        for(auto it : Y[1]){				/// 首先第一个点更新出去
            vis[it] = 1;
            Q.push({0,it,1});
        }
        bfs();						/// bfs(类dijkstra)
        if(dis[n]==LINF)printf("Impossible\n");		/// 跑不到 n点
        else printf("%lld\n",dis[n]);
    }
    return 0;
}

你可能感兴趣的:(图论,算法)