您的位置:首页 > 其它

JZOJ 5459. 【NOIP2017提高A组冲刺11.7】密室

2017-11-07 17:18 501 查看

题目大意

有n个节点m条边,里面有一些不同种类钥匙。如果要通过一条边,需要有特定种类的钥匙(多把)。问最少要经过几条边从1走到n。

数据范围

n≤5000,m≤6000,钥匙的种类k≤10。

题解

条件:

①要求通过一条边有特定种类的钥匙。

②经过的边数最少。

特定的种类?时间复杂度肯定含有2^k,再套什么最短路算法比如spfa。

直接状压DP!!!

但是为什么时间超限?我蠢。

由于队列里已经记录了点x和具备钥匙的状态s,直接拿它去更新其他状态就好了,不必枚举冗余状态。

错误代码(TLE)

while(H<T){
H++;
x=qu[H][0];
kk=qu[H][1];
for(i=head[x];i;i=edge[i].next){
fo(s,0,_2[k+1]-1){
v=s|a[x];
if((v|edge[i].val)!=v)continue;
if(f[edge[i].to][v]>f[x][s]+1){
f[edge[i].to][v]=f[x][s]+1;
if(!bz[edge[i].to][v]){
bz[edge[i].to][v]=1;
qu[++T][0]=edge[i].to;
qu[T][1]=v;
}
}
}
}
bz[x][kk]=0;
}


这个代码的缺点:

①枚举大量冗余状态。

②有松弛操作。

但是这道题目不需要松弛操作,因为第一次更新的解一定是状态的最优解。

所以bfs就好了。

总结

①对于spfa,如果已经有二进制的状态,就不必枚举代表意义相同的二进制状态

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define N 5010
#define M 6010
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
int to,next,val;
};note edge[M];
int head
,tot;
int f
[1030];
int i,j,k,ans,n,m,x,kk,temp;
int u,v,H,T,s;
long long sum,cnt;
int _2[12];
int a
;
int qu[N*3030][2];
void lb(int x,int y,int z){
edge[++tot].to=y;
edge[tot].next=head[x];
edge[tot].val=z;
head[x]=tot;
}
int main(){
freopen("room.in","r",stdin);
freopen("room.out","w",stdout);
_2[1]=1;fo(i,2,11)_2[i]=_2[i-1]*2;
scanf("%d%d%d",&n,&m,&k);
fo(i,1,n){
a[i]=0;
fo(j,1,k){
scanf("%d",&x);
a[i]+=_2[j]*x;
}
}
fo(i,1,m){
scanf("%d%d",&u,&v);
temp=0;
fo(j,1,k){
scanf("%d",&x);
temp+=_2[j]*x;
}
lb(u,v,temp);
}
memset(f,63,sizeof(f));
f[1][a[1]]=0;
H=0,T=1;
qu[1][0]=1;
qu[1][1]=a[1];
while(H<T){
H++;
x=qu[H][0];kk=qu[H][1];
for(i=head[x];i;i=edge[i].next){
if((kk|edge[i].val)!=kk)continue;
v=kk|a[edge[i].to];
if(f[edge[i].to][v]==1061109567){
f[edge[i].to][v]=f[x][kk]+1;
qu[++T][0]=edge[i].to;
qu[T][1]=v;
}
}
}
ans=1061109567;
fo(i,0,_2[k+1]-1)ans=min(ans,f
[i]);
if(ans==1061109567)printf("No Solution");else
printf("%d",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: