Lev OJ P1678
题意:
给定个点和条有向边,每个点都有一个权值,若,则有一条从到的单向边,边的权值都为1,求单源最短路。
首先定义集合为的二进制表示下所有为1的位置的下标组成的集合,例如。则一定有 ,根据子集关系的传递性,我们有,那么原先需要连的三条边便可以通过传递性优化为 。那么对于,我们只需要向所有满足的连边即可,由于这样的最多存在个,那么我们就可以将总边数从优化至,复杂度便可以接受了。
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int maxn = (1 << 21) + 2e5 + 10;
const int maxm = 2e7 + 10;
const int inf = 0x3f3f3f3f;
int base = 1 << 20;
struct Edge {
int val, to, next;
}e[maxm];
int tot;
bool vis[maxn];
int head[maxn], dis[maxn];
int n, m;
struct Node {
int val, num;
bool operator<(const Node& node)const
{
return val > node.val;
}
};
void addedge(int u, int v, int w)
{
e[++tot] = Edge({ w,v,head[u] });
head[u] = tot;
}
void build()
{
for ( int i = 0;i <= base;i++ ) {
for ( int j = 0;j <= 20;j++ ) {
if ( i & (1 << j) )
addedge(i, i ^ (1 << j), 0);
}
}
}
void bfs()
{
priority_queue<Node> pq;
pq.push(Node({ 0,1 + base }));
dis[1 + base] = 0;
while ( !pq.empty() ) {
int temp = pq.top().num;
pq.pop();
// int bb = temp - base;
if ( vis[temp] )
continue;
vis[temp] = 1;
for ( int i = head[temp];~i;i=e[i].next ) {
int to = e[i].to;
// int aa = to - base;
if ( dis[to] > dis[temp] + e[i].val ) {
dis[to] = dis[temp] + e[i].val;
pq.push(Node({ dis[to],to }));
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
int u, v;
memset(head, -1, sizeof head);
memset(dis, inf, sizeof dis);
build();
for ( int i = 1;i <= n;i++ ) {
scanf("%d", &u);
addedge(i + base, u, 0);
addedge(u, i + base, 1);
}
for ( int i = 1;i <= m;i++ ) {
scanf("%d%d", &u, &v);
addedge(u + base, v + base, 1);
}
bfs();
for ( int i = 1;i <= n;i++ ) {
if ( dis[i + base] == inf )
printf("-1\n");
else
printf("%d\n", dis[i + base]);
}
return 0;
}