直接按照题意建图跑最短路即可。
Tips:题目要求的边都是单向边
如果直接按照题意 n 2 n^2 n2建边,很显然会超时。
我们发现 v a l val val最多只有 2 20 2^{20} 220个,而建边的条件即 i − > j ( v a l j ∈ v a l i ) i->j(val_j \in val_i) i−>j(valj∈vali)。
因此,我们可以将第 i i i号点向他的 v a l i val_i vali连一条长度为0的边, v a l i val_i vali 向i连一条长度为1的边。
不仅如此,对于每个 k ( k ∈ 2 20 ) k(k\in2^{20}) k(k∈220),我们将 k k k向k的子集连一条长度为0的边。
经过这样的建图,我们做最短路(或者BFS)就可以满足题目要求。(中间可以不断操作,当到达一个点时就会有1的代价,即从一个点走到另一个点)
#include
using namespace std;
const int N = 20010000;
int n,m;
struct Node{
int y,Next,v;
}e[2*N];
int val[N];
int dis[N];
bool vi[N];
int len = 0 , linkk[N ];
void Insert(int x,int y,int v){
e[++len].Next = linkk[x];
linkk[x] = len;
e[len].y = y;
e[len].v = v;
}
int main(){
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
scanf("%d %d",&n,&m);
int A = n+1;
int maxx = 0;
for (int i = 1; i <= n; i++){
scanf("%d",&val[i]);
maxx = max(maxx,val[i]);
Insert(i,val[i]+A , 0);
Insert(val[i]+A , i , 1);//连边
}
for (int i = 1; i <= maxx; i++)
for (int j = 0; j <= 20; j++)
if (i & (1<<j)) Insert(i+A , (i ^ (1<<j))+A , 0);
for (int i = 1,x,y; i <= m; i++) scanf("%d %d",&x,&y) , Insert(x,y,1);
queue < int > q;
memset(vi,0,sizeof vi);
memset(dis,40,sizeof dis);
dis[1] = 0; q.push(1);vi[1] = 1;
while (!q.empty()){
int x = q.front(); vi[x] = 0;q.pop();
for (int i = linkk[x]; i; i = e[i].Next){
int y = e[i].y , v = e[i].v;
if (dis[y] > dis[x] + v){
dis[y] = dis[x] + v;
if (!vi[y]) q.push(y) , vi[y] = 1;
}
}
}
for (int i = 1; i <= n; i++) printf("%d\n",dis[i] > 10000000?-1:dis[i]);
return 0;
}
我们如果直接按照70pts 的方法建图,就会MLE,我们考虑优化空间。
我们其实没必要将每个点的子集都连边,只需要将需要的边和点连接即可。
因此,我们放弃之前的预处理,在做BFS的时候,每做完一个点 x x x,就枚举 x x x的子集,用 x x x来更新 x x x的子集,这样就可以将每一个空间都利用起来,即满分做法。
#include
using namespace std;
const int N = 20010000;
int n,m;
struct Node{
int y,Next,v;
}e[2*N];
int val[N];
int dis[N];
bool vi[N];
int len = 0 , linkk[N ];
void Insert(int x,int y,int v){
e[++len].Next = linkk[x];
linkk[x] = len;
e[len].y = y;
e[len].v = v;
}
int main(){
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
scanf("%d %d",&n,&m);
int A = n+1;
int maxx = 0;
for (int i = 1; i <= n; i++){
scanf("%d",&val[i]);
maxx = max(maxx,val[i]);
Insert(i,val[i]+A , 0);
Insert(val[i]+A , i , 1);
vi[val[i]] = 1;
}
// for (int i = 1; i <= maxx; i++)
// if (vi[i]){
// for (int j = 0; j <= 20; j++)
// if (i & (1<
// }
for (int i = 1,x,y; i <= m; i++) scanf("%d %d",&x,&y) , Insert(x,y,1);
queue < int > q;
memset(vi,0,sizeof vi);
memset(dis,40,sizeof dis);
dis[1] = 0; q.push(1);vi[1] = 1;
while (!q.empty()){
int x = q.front(); vi[x] = 0;q.pop();
for (int i = linkk[x]; i; i = e[i].Next){
int y = e[i].y , v = e[i].v;
if (dis[y] > dis[x] + v){
dis[y] = dis[x] + v;
if (!vi[y]) q.push(y) , vi[y] = 1;
}
}
if (x > n){
x-=A;
for (int i = 0; i <= 20; i++)
if (x&(1<<i)){
int y = x ^ (1<<i);
if (dis[y+A] > dis[x+A]){
dis[y+A] = dis[x+A];
if (!vi[y+A]) q.push(y+A) , vi[y+A] = 1;
}
}
}
}
for (int i = 1; i <= n; i++) printf("%d\n",dis[i] > 100000000?-1:dis[i]);
return 0;
}