BZOJ1097: [POI2007]旅游景点atr
【传送门:BZOJ1097】
简要题意:
给出n个点,m条边的无向连通图,有k个必经点,有c组关系,每组关系输入x,y,保证x和y为必经点,代表要在x上逗留后才能在y上逗留(可以直接经过y点,这样就不算逗留),必经点的编号为2到k+1,求出从1点开始,经过k个必经点后,到达n点的最短距离
题解:
PS1:要搞清楚,经过一个点不代表在这个点逗留
PS2:为了减次方,在代码处,我把点的编号改为0到n-1,必经点为1到k
dijkstra+状压DP
首先因为k很小,所以可以将每个必经点到所有点的距离都求出来并保存
(AKC:SPFA是logM的,dijkstra是logN的)显然要用dijkstra更优秀
然后考虑DP来做,设f[i][j]为i状态(表示k个必经点是否已经逗留过),最后一个逗留的必经点是j时的最短距离
枚举i的每一个位置,记录0和1出现的位置,然后枚举j就枚举每个1出现的位置
转移的时候,枚举当前要逗留的必经点就枚举0的位置,设为k
那么怎么处理先后逗留顺序呢?
我们设p[i]为要在第i个必经点逗留必须要达到的状态
对于一对先后顺序x,y,就将p[y]|=(1<<(x-1))(这里的x-1是为了减次方)
如果当前状态i&p[k]!=p[k],那就代表应该在k之前逗留的点还有没逗留的,所以i状态时不能逗留k
最后枚举每个必经点作为最后的必经点,求出f[(1<<k)-1][i]+第i个必经点到终点的距离的最小值
参考代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<cstdlib> #include<queue> using namespace std; struct node { int x,y,d,next; }a[410000];int len,last[21000]; void ins(int x,int y,int d) { len++; a[len].x=x;a[len].y=y;a[len].d=d; a[len].next=last[x];last[x]=len; } struct list { int x,d; friend bool operator < (list n1,list n2){return n1.d>n2.d;} }; priority_queue<list> q; int d[31][21000],p[31]; bool v[21000]; int f[1100000][31]; int p0[31],p1[31]; int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int n,m,k; scanf("%d%d%d",&n,&m,&k); if(k==0) { } len=0;memset(last,0,sizeof(last)); for(int i=1;i<=m;i++) { int x,y,d; scanf("%d%d%d",&x,&y,&d);x--,y--; //0~n-1 ins(x,y,d);ins(y,x,d); } memset(d,63,sizeof(d)); for(int i=0;i<=k;i++) { memset(v,false,sizeof(v)); d[i][i]=0; q.push((list){i,0}); while(q.empty()==0) { list tno=q.top();q.pop(); int x=tno.x; if(v[x]==true) continue; v[x]=true; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(d[i][y]>d[i][x]+a[k].d) { d[i][y]=d[i][x]+a[k].d; q.push((list){y,d[i][y]}); } } } } if(k==0){printf("%d\n",d[0][n-1]);return 0;} int c;scanf("%d",&c); for(int i=1;i<=c;i++) { int x,y; scanf("%d%d",&x,&y);x--,y--; p[y]|=1<<(x-1); } memset(f,63,sizeof(f)); for(int i=1;i<=k;i++) if(p[i]==0) f[1<<(i-1)][i]=d[i][0]; for(int i=0;i<(1<<k);i++) { int d0=0,d1=0; for(int j=1;j<=k;j++) { if((i&(1<<(j-1)))==0) p0[++d0]=j; else p1[++d1]=j; } for(int j=1;j<=d1;j++) { for(int k=1;k<=d0;k++) { int x=p1[j],y=p0[k]; if((p[y]&i)!=p[y]) continue; f[i+(1<<(y-1))][y]=min(f[i][x]+d[x][y],f[i+(1<<(y-1))][y]); } } } int ans=1<<30; for(int i=1;i<=k;i++) ans=min(ans,f[(1<<k)-1][i]+d[i][n-1]); printf("%d\n",ans); return 0; }
渺渺时空,茫茫人海,与君相遇,幸甚幸甚