题意简述
给定一张n个点的有向图,一个长度为m的点号序列,表示图中一条路径(不保证是简单路径),要求选出其一个子序列,使得原序列是依次经过该子序列中所有点的一条最短路径,最小化子序列长度并输出该子序列。
算法概述
把原序列变成子序列,要使子序列长度最小化,则对于原序列中的点,当然是能删就删。
动手画一画三个样例,不难发现一个结论:对于原序列中连续的三个点p,v,q,若dis[p,q]=dis[p,v]+dis[v,q],则v点可以删去。其中dis[i,j]表示点i,j之间的最短路径。
结论的正确性不难证明,由于满足上述等式,则p->v->q是p,q之间的一条最短路径,则可以将v删去,充分性成立。
若可以将v删去,则说明p->v->q是p,q之间的一条最短路径,那么必然满足dis[p,q]=dis[p,v]+dis[v,q],必要性成立。
故结论成立。
有了上述结论,我们就可以直接用Floyd算法预处理出任意两点间的最短路径,然后依次遍历原序列中每个点,判断若该点是否可以删去,能删则删。由于删点之后还可能要用到该点的前驱节点,普通队列无法处理,故我们可以用链表处理这个序列。
时间复杂度O(n3+m)。
参考代码
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; const int N=110,M=1e6+10; struct Node{ int pre,nex,dat; #define pre(p) pot[p].pre #define nex(p) pot[p].nex #define dat(p) pot[p].dat }pot[M]; void remove(int p) { pre(nex(p))=pre(p); nex(pre(p))=nex(p); pre(p)=nex(p)=-1; } void insert(int x,int p,int val) { pre(p)=x; nex(p)=-1; nex(x)=p; dat(p)=val; } int g[N][N]; int dis[N][N]; int n,m; void floyd() { memset(dis,0x3f,sizeof dis); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(i==j)dis[i][j]=0; else if(g[i][j]==1)dis[i][j]=1; } for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(dis[i][j]>dis[i][k]+dis[k][j]) dis[i][j]=dis[i][k]+dis[k][j]; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%1d",&g[i][j]); floyd(); scanf("%d",&m); pre(0)=-1; for(int i=1;i<=m;i++) { int p;scanf("%d",&p); insert(i-1,i,p); } int cnt=m; for(int i=2;i<=m-1;i++) { int last=dat(pre(i)),next=dat(nex(i)); int now=dat(i); if(dis[last][next]==dis[last][now]+dis[now][next])remove(i),cnt--; } printf("%d ",cnt); for(int i=nex(0);~i;i=nex(i)) printf("%d ",dat(i)); return 0; }